Snippet #1525

TTL: forever — WordwrapView raw

on 2021/07/27 15:06:39 (UTC) by V. V. as Python

  1. #!/usr/bin/python3
  2. # coding=utf-8 license=Apache-2.0
  3.  
  4. """
  5. Tool to follow caret position reported by applications.
  6.  
  7. Listens to caret move events and prints and displays the reported positions to
  8. ease debugging apps and toolkits.
  9. """
  10.  
  11. import pyatspi
  12.  
  13. import cairo
  14. import gi
  15. gi.require_version('Gtk', '3.0')
  16. from gi.repository import Gtk, GLib
  17.  
  18.  
  19. class BaseListener:
  20.     def __init__(self, types):
  21.         self.__types = types
  22.  
  23.     def __enter__(self):
  24.         for t in self.__types:
  25.             pyatspi.Registry.registerEventListener(self._on_event, t)
  26.         pyatspi.Registry.start()
  27.  
  28.     def __exit__(self, *args, **kwargs):
  29.         pyatspi.Registry.stop()
  30.         for t in self.__types:
  31.             pyatspi.Registry.deregisterEventListener(self._on_event, t)
  32.  
  33.     def _on_event(self, event):
  34.         return False
  35.  
  36.  
  37. def main(listener):
  38.     try:
  39.         with listener as dummy:
  40.             pass
  41.     except KeyboardInterrupt:
  42.         pass
  43.  
  44.  
  45. class Highlight(Gtk.Window):
  46.     """ A overlay to highlight an area on screen.
  47.     TODO: make the window input-less """
  48.     def __init__(self, x, y, w, h):
  49.         super().__init__(type=Gtk.WindowType.POPUP, app_paintable=True)
  50.         self.set_default_size(w, h)
  51.         self.move(x, y)
  52.  
  53.         self.do_screen_changed(None)
  54.  
  55.     def do_screen_changed(self, previous_screen):
  56.         screen = self.get_screen()
  57.         visual = screen.get_rgba_visual()
  58.         if visual:
  59.             self.set_visual(visual)
  60.  
  61.     def do_draw(self, cr):
  62.         cr.set_source_rgba(1, 0, 0, 0.2)
  63.         cr.set_operator(cairo.OPERATOR_SOURCE)
  64.         cr.paint()
  65.         cr.set_source_rgba(1, 0, 0, 0.8)
  66.         alloc = self.get_allocation()
  67.         cr.rectangle(alloc.x, alloc.y, alloc.width, alloc.height)
  68.         cr.stroke()
  69.         return True
  70.  
  71.  
  72. class Listener(BaseListener):
  73.     def __init__(self, apps=None):
  74.         super().__init__(types=[
  75.             "object:text-caret-moved",
  76.         ])
  77.  
  78.         self.apps = [app.casefold() for app in apps] if apps else []
  79.  
  80.         self.area = None
  81.         self.area_clear_id = 0
  82.  
  83.     def _update_area(self, extents):
  84.         def clear():
  85.             if self.area:
  86.                 self.area.destroy()
  87.                 self.area = None
  88.             self.area_clear_id = 0
  89.             return False
  90.  
  91.         if self.area:
  92.             self.area.destroy()
  93.             GLib.source_remove(self.area_clear_id)
  94.         self.area = Highlight(*extents)
  95.         self.area.show()
  96.         self.area_clear_id = GLib.timeout_add(2000, clear)
  97.  
  98.     def _on_event(self, event):
  99.         if super()._on_event(event):
  100.             return True
  101.  
  102.         if event.type != "object:text-caret-moved":
  103.             return False
  104.  
  105.         if self.apps and event.host_application.name.casefold() not in self.apps:
  106.             return False
  107.  
  108.         text = event.source.queryText()
  109.         extents = text.getCharacterExtents(event.detail1, pyatspi.DESKTOP_COORDS)
  110.  
  111.         print(event)
  112.         print("\tx=%s y=%s w=%s h=%s" % extents)
  113.  
  114.         self._update_area(extents)
  115.  
  116.         return True
  117.  
  118.  
  119. if __name__ == '__main__':
  120.     from sys import argv
  121.  
  122.     main(Listener(apps=(argv[1:] if len(argv) > 1 else None)))

Recent Snippets