I ended up solving this by cheating a bit and dropping into C to add an event listener that calls back into Python. I wrote this short C file and compiled it into a shared object:
typedef void (*callback)(WebKitDOMNode* node);
static bool handler(WebKitDOMNode* node, WebKitDOMEvent* event, callback cb) {
cb(node);
return true;
}
void add_event_listener(WebKitDOMEventTarget* target, callback focused, callback blurred) {
webkit_dom_event_target_add_event_listener(target, "focus", G_CALLBACK(handler), false, (void*)focused);
webkit_dom_event_target_add_event_listener(target, "blur", G_CALLBACK(handler), false, (void*)blurred);
}
I stole some code from this answer to convert between Python objects and C pointers, and adapted it a little:
class _PyGObject_Functions(ctypes.Structure):
_fields_ = [
('register_class', ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.py_object, ctypes.py_object)),
('register_wrapper', ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)),
('lookup_class', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
('newgobj', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
]
class PyGObjectCPAI(object):
def __init__(self):
PyCObject_AsVoidPtr = ctypes.pythonapi.PyCObject_AsVoidPtr
PyCObject_AsVoidPtr.restype = ctypes.c_void_p
PyCObject_AsVoidPtr.argtypes = [ctypes.py_object]
addr = PyCObject_AsVoidPtr(ctypes.py_object(gi._gobject._PyGObject_API))
self._api = _PyGObject_Functions.from_address(addr)
def pygobject_new(self, addr):
return self._api.newgobj(addr)
def cToPython(ptr, capi = PyGObjectCPAI()):
return capi.pygobject_new(ptr)
def pythonToC(obj):
return hash(obj)
I wrote a python event listener:
def focusChanged(node, focused):
....
Made C-callable functions (they need to be global so they won't get garbage collected):
native = ctypes.cdll.LoadLibrary('native.so')
native.add_event_listener.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
def focused(nodeAddr): focusChanged(cToPython(nodeAddr), True)
def blurred(nodeAddr): focusChanged(cToPython(nodeAddr), False)
CALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
focused = CALLBACK(focused)
blurred = CALLBACK(blurred)
And added the event listener to each node:
document = view.get_dom_document()
nodes = document.get_elements_by_tag_name('*')
for i in range(nodes.get_length()):
node = nodes.item(i)
if isinstance(node, WebKit.DOMHTMLInputElement) or isinstance(node, WebKit.DOMHTMLTextAreaElement):
native.add_event_listener(pythonToC(node), focused, blurred)