"""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()