1import gi 2 3from gi.repository import Gtk as gtk 4from gi.repository import Gdk as gdk 5from gi.repository import Wnck as wnck 6 7from accerciser.plugin import Plugin 8from accerciser.i18n import N_, _ 9 10import pyatspi 11 12class QuickSelect(Plugin): 13 ''' 14 Plugin class for quick select. 15 ''' 16 plugin_name = N_('Quick Select') 17 plugin_name_localized = _(plugin_name) 18 plugin_description = \ 19 N_('Plugin with various methods of selecting accessibles quickly.') 20 21 def init(self): 22 ''' 23 Initialize plugin. 24 ''' 25 self.global_hotkeys = [(N_('Inspect last focused accessible'), 26 self._inspectLastFocused, 27 gdk.KEY_a, gdk.ModifierType.CONTROL_MASK | \ 28 gdk.ModifierType.MOD1_MASK), 29 (N_('Inspect accessible under mouse'), 30 self._inspectUnderMouse, 31 gdk.KEY_question, gdk.ModifierType.CONTROL_MASK | \ 32 gdk.ModifierType.MOD1_MASK)] 33 34 pyatspi.Registry.registerEventListener(self._accEventFocusChanged, 35 'object:state-changed') 36 37 pyatspi.Registry.registerEventListener(self._accEventSelectionChanged, 38 'object:selection-changed') 39 40 self.last_focused = None 41 self.last_selected = None 42 43 def _accEventFocusChanged(self, event): 44 ''' 45 Hold a reference for the last focused accessible. This is used when a certain 46 global hotkey is pressed to select this accessible. 47 48 @param event: The event that is being handled. 49 @type event: L{pyatspi.event.Event} 50 ''' 51 if event.type != "object:state-changed:focused" and \ 52 event.type != "object:state-changed:selected": 53 return 54 55 if event.detail1 != 1: 56 return 57 58 if not self.isMyApp(event.source): 59 self.last_focused = event.source 60 61 def _accEventSelectionChanged(self, event): 62 ''' 63 Hold a reference for the last parent of a selected accessible. 64 This will be useful if we want to find an accessible at certain coords. 65 66 @param event: The event that is being handled. 67 @type event: L{pyatspi.event.Event} 68 ''' 69 if not self.isMyApp(event.source): 70 self.last_selected = event.source 71 72 def _inspectLastFocused(self): 73 ''' 74 Inspect the last focused widget's accessible. 75 ''' 76 if self.last_focused: 77 self.node.update(self.last_focused) 78 79 def _inspectUnderMouse(self): 80 ''' 81 Inspect accessible of widget under mouse. 82 ''' 83 display = gdk.Display.get_default() 84 screen, x, y, flags = display.get_pointer() 85 del screen # A workaround http://bugzilla.gnome.org/show_bug.cgi?id=593732 86 87 # First check if the currently selected accessible has the pointer over it. 88 # This is an optimization: Instead of searching for 89 # STATE_SELECTED and ROLE_MENU and LAYER_POPUP in the entire tree. 90 item = self._getPopupItem(x, y) 91 if item: 92 self.node.update(item) 93 return 94 95 # Inspect accessible under mouse 96 desktop = pyatspi.Registry.getDesktop(0) 97 wnck_screen = wnck.Screen.get_default() 98 window_order = [w.get_name() for w in wnck_screen.get_windows_stacked()] 99 top_window = (None, -1) 100 for app in desktop: 101 if not app or self.isMyApp(app): 102 continue 103 for frame in app: 104 if not frame: 105 continue 106 acc = self._getComponentAtCoords(frame, x, y) 107 if acc: 108 try: 109 z_order = window_order.index(frame.name) 110 except ValueError: 111 # It's possibly a popup menu, so it would not be in our frame name 112 # list. And if it is, it is probably the top-most component. 113 try: 114 if acc.queryComponent().getLayer() == pyatspi.LAYER_POPUP: 115 self.node.update(acc) 116 return 117 except: 118 pass 119 else: 120 if z_order > top_window[1]: 121 top_window = (acc, z_order) 122 123 if top_window[0]: 124 self.node.update(top_window[0]) 125 126 def _getPopupItem(self, x, y): 127 suspect_children = [] 128 # First check if the currently selected accessible has the pointer over it. 129 # This is an optimization: Instead of searching for 130 # STATE_SELECTED and ROLE_MENU and LAYER_POPUP in the entire tree. 131 if self.last_selected and \ 132 self.last_selected.getRole() == pyatspi.ROLE_MENU and \ 133 self.last_selected.getState().contains(pyatspi.STATE_SELECTED): 134 try: 135 si = self.last_selected.querySelection() 136 except NotImplementedError: 137 return None 138 139 if si.nSelectedChildren > 0: 140 suspect_children = [si.getSelectedChild(0)] 141 else: 142 suspect_children = self.last_selected 143 144 for child in suspect_children: 145 try: 146 ci = child.queryComponent() 147 except NotImplementedError: 148 continue 149 150 if ci.contains(x, y, pyatspi.DESKTOP_COORDS) and \ 151 ci.getLayer() == pyatspi.LAYER_POPUP: 152 return child 153 154 return None 155 156 def _getComponentAtCoords(self, parent, x, y): 157 ''' 158 Gets any child accessible that resides under given desktop coordinates. 159 160 @param parent: Top-level accessible. 161 @type parent: L{Accessibility.Accessible} 162 @param x: X coordinate. 163 @type x: integer 164 @param y: Y coordinate. 165 @type y: integer 166 167 @return: Child accessible at given coordinates, or None. 168 @rtype: L{Accessibility.Accessible} 169 ''' 170 container = parent 171 while True: 172 container_role = container.getRole() 173 if container_role == pyatspi.ROLE_PAGE_TAB_LIST: 174 try: 175 si = container.querySelection() 176 container = si.getSelectedChild(0)[0] 177 except NotImplementedError: 178 pass 179 try: 180 ci = container.queryComponent() 181 except: 182 break 183 else: 184 inner_container = container 185 container = ci.getAccessibleAtPoint(x, y, pyatspi.DESKTOP_COORDS) 186 if not container or container.queryComponent() == ci: 187 # The gecko bridge simply has getAccessibleAtPoint return itself 188 # if there are no further children 189 break 190 if inner_container == parent: 191 return None 192 else: 193 return inner_container 194 195