126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""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()
|