Domanda

I'm converting a WebKitGTK+ project from C++ to Python (using PyGI). I need to detect any time an <input> element on the page gains or loses focus. In the C++ project I did it using this code (based on this example):

WebKitDOMDocument* document = webkit_web_view_get_dom_document(view);
WebKitDOMNodeList* nodes = webkit_dom_document_get_elements_by_tag_name(document, "*");
int len = webkit_dom_node_list_get_length(nodes);
for(int i = 0; i < len; i++) {
    WebKitDOMNode* node = webkit_dom_node_list_item(nodes, i);
    if(WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(node)) {
        webkit_dom_event_target_add_event_listener(WEBKIT_DOM_EVENT_TARGET(node), "focus", G_CALLBACK(inputFocus), false);
        webkit_dom_event_target_add_event_listener(WEBKIT_DOM_EVENT_TARGET(node), "blur", G_CALLBACK(inputBlur), false);
    }
}

I tried to do the same thing in Python and came up with:

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):
        node.add_event_listener('focus', inputFocus, False)

The problem is, DOMHTMLInputElement doesn't have an add_event_listener method. Based on the C bindings, it should be part of DOMEventTarget (which DOMHTMLInputElement extends), but it's missing along with remove_event_listener, although dispatch_event somehow made it in. It might be due to this WebKit bug; I'm not certain

Is there any way to do this in Python? It doesn't have to use the same scheme I was using in C++, I just need some way to register a callback that invokes every time focus changes on certain elements on the page

È stato utile?

Soluzione 2

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)

Altri suggerimenti

My understanding is those functions are not exposed for introspection and hence not available in python. I also came across same problem and found a hacky way to get around the problem. It did work for me.

  1. Use javascript to listen for the events you are interested. In the javascript event handler function call alert function e.g.

    alert("FOCUS:"+ node.getId()); //Or name or whatever that can help python side to identify

  2. On python side use alert handler to get the message and parse the message to figure out which node

This won't work if you can not identify the unique node either by name/id or xpath, but if you are controlling the html, you can easily handle this problem.

My example code can be found at https://github.com/nhrdl/notesMD/blob/master/notesmd.py. Take a look at alert function in the file. HTML files just call alert and python side parses the message and acts on it.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top