Cthulhu is no longer a fork. Now it is a screen reader written in Golang.

This commit is contained in:
Storm Dragon committed 2026-10-14 00:16:34 -04:00
1 parent a998fe67de
commit 069fc674dc
708 files changed
+112482 -532

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
"""Painted OCR text with accessible focus targets on a private Xorg display."""
import json
import os
from pathlib import Path
import sys
import cairo
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_version("GdkX11", "3.0")
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import Gdk, GdkPixbuf, GdkX11, GLib, Gtk
from Xlib import X, Xatom, display
def main() -> None:
window = Gtk.Window(title="OCR session fixture")
window.set_decorated(False)
window.move(20, 20)
fixed = Gtk.Fixed()
window.add(fixed)
painted = Gtk.DrawingArea()
painted.set_size_request(640, 220)
painted.set_can_focus(True)
painted.get_accessible().set_name("Painted OCR target")
imagePath = Path(__file__).resolve().parents[1] / "internal/ocr/testdata/painted-text.png"
pixels = GdkPixbuf.Pixbuf.new_from_file(str(imagePath))
def draw(_widget: Gtk.Widget, context: object) -> bool:
Gdk.cairo_set_source_pixbuf(context, pixels, 0, 0)
context.paint()
return False
painted.connect("draw", draw)
fixed.put(painted, 0, 0)
password = Gtk.Entry()
password.set_visibility(False)
password.get_accessible().set_name("Protected OCR focus")
fixed.put(password, 0, 230)
other = Gtk.Button(label="Other OCR focus")
fixed.put(other, 280, 230)
window.show_all()
painted.grab_focus()
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.set_image(pixels)
connection = display.Display()
root = connection.screen().root
clientId = GdkX11.X11Window.get_xid(window.get_window())
client = connection.create_resource_object("window", clientId)
root.change_property(connection.intern_atom("_NET_ACTIVE_WINDOW"), Xatom.WINDOW, 32, [clientId])
client.change_property(connection.intern_atom("_NET_WM_PID"), Xatom.CARDINAL, 32, [os.getpid()])
client.set_input_focus(X.RevertToParent, X.CurrentTime)
connection.sync()
activeClient = client
rawClient = None
def show_inaccessible_window() -> None:
nonlocal activeClient, rawClient
# A raw X11 client deliberately has no AT-SPI object or focused widget.
surface = cairo.ImageSurface.create_from_png(str(imagePath))
width, height = surface.get_width(), surface.get_height()
rawClient = root.create_window(20, 20, width, height, 0,
connection.screen().root_depth,
X.InputOutput, X.CopyFromParent,
background_pixel=0xFFFFFF)
rawClient.set_wm_name("Inaccessible OCR fixture")
rawClient.set_wm_class("example", "example")
rawClient.change_property(connection.intern_atom("_NET_WM_PID"),
Xatom.CARDINAL, 32, [os.getpid()])
rawClient.map()
rawClient.configure(stack_mode=X.Above)
graphics = rawClient.create_gc()
data = bytes(surface.get_data())
stride = surface.get_stride()
for row in range(0, height, 16):
rows = min(16, height - row)
rawClient.put_image(graphics, 0, row, width, rows, X.ZPixmap,
connection.screen().root_depth, 0,
data[row * stride:(row + rows) * stride])
graphics.free()
root.change_property(connection.intern_atom("_NET_ACTIVE_WINDOW"),
Xatom.WINDOW, 32, [rawClient.id])
rawClient.set_input_focus(X.RevertToParent, X.CurrentTime)
activeClient = rawClient
def state() -> bool:
connection.sync()
print(json.dumps({"window": activeClient.id, "focus": connection.get_input_focus().focus.id}), flush=True)
return False
def command_ready(_source: object, condition: int) -> bool:
if condition & GLib.IO_HUP:
Gtk.main_quit()
return False
line = sys.stdin.readline()
if not line:
Gtk.main_quit()
return False
action = json.loads(line)["action"]
if action == "painted":
painted.grab_focus()
elif action == "password":
password.grab_focus()
elif action == "other":
other.grab_focus()
elif action == "clipboard-text":
clipboard.set_text("Generic replacement text", -1)
elif action == "inaccessible":
show_inaccessible_window()
elif action != "state":
raise ValueError("Unknown fixture action")
GLib.idle_add(state)
return True
GLib.idle_add(state)
GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_HUP, command_ready)
Gtk.main()
if rawClient is not None:
rawClient.destroy()
connection.close()
if __name__ == "__main__":
main()