From kon at iki.fi Sat Sep 2 17:55:10 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Sun, 03 Sep 2006 02:55:10 +0300 Subject: [elinks-dev] Re: UTF-8: fix scrolling of input fields In-Reply-To: <87y7t9oonr.fsf@Astalo.kon.iki.fi> References: <87y7t9oonr.fsf@Astalo.kon.iki.fi> Message-ID: <87y7t1g6xt.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060903/43a9b83e/attachment.bin From witekfl at poczta.onet.pl Sat Sep 9 10:07:16 2006 From: witekfl at poczta.onet.pl (Witold Filipczyk) Date: Sat, 9 Sep 2006 18:07:16 +0200 Subject: [elinks-dev] What is 'sparse' and where to get it? Message-ID: <20060909160716.GA24795@pldmachine> Hi! make check uses 'sparse'. What is 'sparse', what does it do and where to get it? -- Witek From miciah at myrealbox.com Sat Sep 9 10:52:40 2006 From: miciah at myrealbox.com (Miciah Dashiel Butler Masters) Date: Sat, 9 Sep 2006 16:52:40 +0000 Subject: [elinks-dev] What is 'sparse' and where to get it? In-Reply-To: <20060909160716.GA24795@pldmachine> References: <20060909160716.GA24795@pldmachine> Message-ID: <20060909164931.GA5442@FroodyComp.localdomain> On Sat, Sep 09, 2006 at 06:07:16PM +0200, Witold Filipczyk wrote: > Hi! > make check uses 'sparse'. > What is 'sparse', what does it do and where to get it? > -- > Witek Here is an article with links: http://lwn.net/Articles/87538/ -- Miciah Masters / From joey at joeysmith.com Wed Sep 13 01:39:24 2006 From: joey at joeysmith.com (joey) Date: Wed, 13 Sep 2006 01:39:24 -0600 Subject: [elinks-dev] Crash bug and proposed patch Message-ID: <20060913073924.GB17061@joeysmith.com> I recently ran into a crash bug with elinks. I've provided a backtrace, a sample document, and a proposed patch. Basically, html_special() in src/document/html/renderer.c sets document->refresh without checking to see if document is a valid pointer first (inside the SP_REFRESH case). http://joeysmith.com/elinks-patch.tar.bz2 contains: 1) nullptr.patch - proposed patch 2) bt.txt - GDB backtrace from my debian (sid) system 3) crash.html - The file that exposed the bug I'm providing it this way because linuxfromscratch.org's spamassassin won't let me attach the files directly. From kon at iki.fi Wed Sep 13 14:24:35 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Wed, 13 Sep 2006 23:24:35 +0300 Subject: [elinks-dev] Re: Crash bug and proposed patch In-Reply-To: <20060913073924.GB17061@joeysmith.com> References: <20060913073924.GB17061@joeysmith.com> Message-ID: <8764fro6po.fsf@Astalo.kon.iki.fi> An HTML attachment was scrubbed... URL: http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060913/33d28652/attachment.html -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060913/33d28652/attachment.bin From levinsm at users.sourceforge.net Tue Sep 19 10:27:19 2006 From: levinsm at users.sourceforge.net (M. Levinson) Date: Tue, 19 Sep 2006 12:27:19 -0400 Subject: [elinks-dev] [patch] additional functionality for Python backend Message-ID: <1117.1158683239@poultrygeist.com> This patch is an overhaul of the Python scripting backend. Some of the existing code has been rewritten for robustness and/or clarity (particularly in scripting/python/hooks.c), but the bulk of the patch is new functionality allowing Python code to... - be invoked from keystroke bindings - get information about the currently displayed document - create simple dialog boxes and menus - load documents into the cache - open documents, roughly comparable to running "elinks -remote openURL(...)" The patch includes documentation for Python programmers in doc/python.txt, and the documentation is also available internally from Python code via Python's introspection API. The example hooks.py file in contrib/python has been rewritten to use more idiomatic Python, and I've also added demonstrations of some of the new functionality. Just for fun, one of those demos (bound to the '!' key) can use an add-on Python module from http://feedparser.org/ to parse a list of your favorite RSS/ATOM feeds, figure out which entries you haven't seen yet, open each of those entries in a new tab, and report statistics on what it fetched. I hope somebody finds it at least mildly entertaining. :-) Thanks again for a wonderful browser! src/scripting/python/Makefile | 11 + src/scripting/python/dialogs.h | 8 + src/scripting/python/dialogs.c | 242 +++++++++++++++++++++ src/scripting/python/document.h | 8 + src/scripting/python/document.c | 144 +++++++++++++ src/scripting/python/keybinding.h | 9 + src/scripting/python/keybinding.c | 198 +++++++++++++++++ src/scripting/python/load.h | 8 + src/scripting/python/load.c | 167 +++++++++++++++ src/scripting/python/menu.h | 8 + src/scripting/python/menu.c | 241 +++++++++++++++++++++ src/scripting/python/open.h | 8 + src/scripting/python/open.c | 92 ++++++++ src/scripting/python/python.c | 3 src/scripting/python/core.h | 11 + src/scripting/python/core.c | 172 +++++++++++---- src/scripting/python/hooks.c | 426 +++++++++++++++++-------------------- contrib/python/hooks.py | 326 +++++++++++++++++++++++----- doc/python.txt | 253 ++++++++++++++++++++++ contrib/python/README.Python | 8 - 20 files changed, 2005 insertions(+), 338 deletions(-) diff --git a/src/scripting/python/Makefile b/src/scripting/python/Makefile index 486eddd..a84ed9e 100644 --- a/src/scripting/python/Makefile +++ b/src/scripting/python/Makefile @@ -3,6 +3,15 @@ include $(top_builddir)/Makefile.config INCLUDES += $(PYTHON_CFLAGS) -OBJS = python.o hooks.o core.o +OBJS = \ + core.o \ + dialogs.o \ + document.o \ + hooks.o \ + keybinding.o \ + load.o \ + menu.o \ + open.o \ + python.o include $(top_srcdir)/Makefile.lib diff --git a/src/scripting/python/dialogs.h b/src/scripting/python/dialogs.h new file mode 100644 index 0000000..8f203d1 --- /dev/null +++ b/src/scripting/python/dialogs.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_PYTHON_DIALOGS_H +#define EL__SCRIPTING_PYTHON_DIALOGS_H + +#include + +int python_init_dialogs_interface(PyObject *dict, PyObject *name); + +#endif diff --git a/src/scripting/python/dialogs.c b/src/scripting/python/dialogs.c new file mode 100644 index 0000000..56e5a87 --- /dev/null +++ b/src/scripting/python/dialogs.c @@ -0,0 +1,242 @@ +/* Dialog boxes for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "bfu/inpfield.h" +#include "bfu/msgbox.h" +#include "intl/gettext/libintl.h" +#include "scripting/python/core.h" +#include "session/session.h" +#include "util/error.h" +#include "util/memlist.h" +#include "util/memory.h" +#include "util/string.h" + +/* Python interface for displaying information to the user. */ + +static char python_info_box_doc[] = \ +"info_box(text[, title]) -> None\n\ +\n\ +Display information to the user in a dialog box.\n\ +\n\ +Arguments:\n\ +\n\ +text -- The text to be displayed in the dialog box. This argument can\n\ + be a string or any object that has a string representation as\n\ + returned by str(object).\n\ +\n\ +Optional arguments:\n\ +\n\ +title -- A string containing a title for the dialog box. By default\n\ + the string \"Info\" is used.\n"; + +static PyObject * +python_info_box(PyObject *self, PyObject *args, PyObject *kwargs) +{ + /* [gettext_accelerator_context(python_info_box)] */ + unsigned char *title = N_("Info"); + PyObject *object, *string_object; + unsigned char *text; + static char *kwlist[] = {"text", "title", NULL}; + + if (!python_ses) { + PyErr_SetString(python_elinks_err, "No session"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|s:info_box", kwlist, + &object, &title)) + return NULL; + + assert(object); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + /* + * Get a string representation of the object, then copy that string's + * contents. + */ + string_object = PyObject_Str(object); + if (!string_object) return NULL; + text = (unsigned char *) PyString_AS_STRING(string_object); + if (!text) { + Py_DECREF(string_object); + return NULL; + } + text = stracpy(text); + Py_DECREF(string_object); + if (!text) goto mem_error; + + title = stracpy(title); + if (!title) goto free_text; + + (void) msg_box(python_ses->tab->term, getml(title, NULL), + MSGBOX_NO_INTL | MSGBOX_SCROLLABLE | MSGBOX_FREE_TEXT, + title, ALIGN_LEFT, + text, + NULL, 1, + N_("~OK"), NULL, B_ENTER | B_ESC); + + Py_INCREF(Py_None); + return Py_None; + +free_text: + mem_free(text); + +mem_error: + return PyErr_NoMemory(); +} + +struct python_input_callback_hop { + struct session *ses; + PyObject *callback; +}; + +/* C wrapper that invokes Python callbacks for input_dialog() OK button. */ + +static void +invoke_input_ok_callback(void *data, unsigned char *text) +{ + struct python_input_callback_hop *hop = data; + struct session *saved_python_ses = python_ses; + PyObject *result; + + assert(hop && hop->callback); + if_assert_failed return; + + python_ses = hop->ses; + + /* If @text is NULL, the "s" format will create a None reference. */ + result = PyObject_CallFunction(hop->callback, "s", text); + if (result) + Py_DECREF(result); + else + alert_python_error(); + + Py_DECREF(hop->callback); + mem_free(hop); + + python_ses = saved_python_ses; +} + +/* C wrapper that invokes Python callbacks for input_dialog() cancel button. */ + +static void +invoke_input_cancel_callback(void *data) +{ + invoke_input_ok_callback(data, NULL); +} + +/* Python interface for getting input from the user. */ + +static char python_input_box_doc[] = \ +"input_box(prompt, callback, title=\"User dialog\", initial=\"\") -> None\n\ +\n\ +Display a dialog box to prompt for user input.\n\ +\n\ +Arguments:\n\ +\n\ +prompt -- A string containing a prompt for the dialog box.\n\ +callback -- A callable object to be called after the dialog is\n\ + finished. It will be called with a single argument, which\n\ + will be either a string provided by the user or else None\n\ + if the user canceled the dialog.\n\ +\n\ +Optional keyword arguments:\n\ +\n\ +title -- A string containing a title for the dialog box. By default\n\ + the string \"User dialog\" is used.\n\ +initial -- A string containing an initial value for the text entry\n\ + field. By default the entry field is initially empty.\n"; + +static PyObject * +python_input_box(PyObject *self, PyObject *args, PyObject *kwargs) +{ + unsigned char *prompt; + PyObject *callback; + unsigned char *title = N_("User dialog"); + unsigned char *initial = NULL; + struct python_input_callback_hop *hop; + static char *kwlist[] = {"prompt", "callback", "title", "initial", NULL}; + + if (!python_ses) { + PyErr_SetString(python_elinks_err, "No session"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|ss:input_box", + kwlist, &prompt, &callback, &title, + &initial)) + return NULL; + + assert(prompt && callback && title); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + prompt = stracpy(prompt); + if (!prompt) goto mem_error; + + title = stracpy(title); + if (!title) goto free_prompt; + + if (initial) { + initial = stracpy(initial); + if (!initial) goto free_title; + } + + hop = mem_alloc(sizeof(*hop)); + if (!hop) goto free_initial; + hop->ses = python_ses; + hop->callback = callback; + Py_INCREF(callback); + + input_dialog(python_ses->tab->term, getml(prompt, title, initial, NULL), + title, prompt, + hop, NULL, + MAX_STR_LEN, initial, 0, 0, NULL, + invoke_input_ok_callback, + invoke_input_cancel_callback); + + Py_INCREF(Py_None); + return Py_None; + +free_initial: + mem_free_if(initial); + +free_title: + mem_free(title); + +free_prompt: + mem_free(prompt); + +mem_error: + return PyErr_NoMemory(); +} + +static PyMethodDef dialogs_methods[] = { + {"info_box", (PyCFunction) python_info_box, + METH_VARARGS | METH_KEYWORDS, + python_info_box_doc}, + + {"input_box", (PyCFunction) python_input_box, + METH_VARARGS | METH_KEYWORDS, + python_input_box_doc}, + + {NULL, NULL, 0, NULL} +}; + +int +python_init_dialogs_interface(PyObject *dict, PyObject *name) +{ + return add_python_methods(dict, name, dialogs_methods); +} diff --git a/src/scripting/python/document.h b/src/scripting/python/document.h new file mode 100644 index 0000000..63915be --- /dev/null +++ b/src/scripting/python/document.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_PYTHON_DOCUMENT_H +#define EL__SCRIPTING_PYTHON_DOCUMENT_H + +#include + +int python_init_document_interface(PyObject *dict, PyObject *name); + +#endif diff --git a/src/scripting/python/document.c b/src/scripting/python/document.c new file mode 100644 index 0000000..a096670 --- /dev/null +++ b/src/scripting/python/document.c @@ -0,0 +1,144 @@ +/* Information about current document and current link for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "cache/cache.h" +#include "scripting/python/core.h" +#include "session/location.h" +#include "session/session.h" + +/* Python interface to get the current document's body. */ + +static char python_current_document_doc[] = \ +"current_document() -> string or None\n\ +\n\ +If a document is being viewed, return its body; otherwise return None.\n"; + +static PyObject * +python_current_document(PyObject *self, PyObject *args) +{ + if (python_ses && have_location(python_ses)) { + struct cache_entry *cached = find_in_cache(cur_loc(python_ses)->vs.uri); + struct fragment *f = cached ? cached->frag.next : NULL; + + if (f) return PyString_FromStringAndSize(f->data, f->length); + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* Python interface to get the current document's header. */ + +static char python_current_header_doc[] = \ +"current_header() -> string or None\n\ +\n\ +If a document is being viewed and it has a header, return the header;\n\ +otherwise return None.\n"; + +static PyObject * +python_current_header(PyObject *self, PyObject *args) +{ + if (python_ses && have_location(python_ses)) { + struct cache_entry *cached = find_in_cache(cur_loc(python_ses)->vs.uri); + + if (cached && cached->head) + return PyString_FromString(cached->head); + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* Python interface to get the currently-selected link's URL. */ + +static char python_current_link_url_doc[] = \ +"current_link_url() -> string or None\n\ +\n\ +If a link is selected, return its URL; otherwise return None.\n"; + +static PyObject * +python_current_link_url(PyObject *self, PyObject *args) +{ + unsigned char url[MAX_STR_LEN]; + + if (python_ses && get_current_link_url(python_ses, url, MAX_STR_LEN)) + return PyString_FromString(url); + + Py_INCREF(Py_None); + return Py_None; +} + +/* Python interface to get the current document's title. */ + +static char python_current_title_doc[] = \ +"current_title() -> string or None\n\ +\n\ +If a document is being viewed, return its title; otherwise return None.\n"; + +static PyObject * +python_current_title(PyObject *self, PyObject *args) +{ + unsigned char title[MAX_STR_LEN]; + + if (python_ses && get_current_title(python_ses, title, MAX_STR_LEN)) + return PyString_FromString(title); + + Py_INCREF(Py_None); + return Py_None; +} + +/* Python interface to get the current document's URL. */ + +static char python_current_url_doc[] = \ +"current_url() -> string or None\n\ +\n\ +If a document is being viewed, return its URL; otherwise return None.\n"; + +static PyObject * +python_current_url(PyObject *self, PyObject *args) +{ + unsigned char url[MAX_STR_LEN]; + + if (python_ses && get_current_url(python_ses, url, MAX_STR_LEN)) + return PyString_FromString(url); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef document_methods[] = { + {"current_document", python_current_document, + METH_NOARGS, + python_current_document_doc}, + + {"current_header", python_current_header, + METH_NOARGS, + python_current_header_doc}, + + {"current_link_url", python_current_link_url, + METH_NOARGS, + python_current_link_url_doc}, + + {"current_title", python_current_title, + METH_NOARGS, + python_current_title_doc}, + + {"current_url", python_current_url, + METH_NOARGS, + python_current_url_doc}, + + {NULL, NULL, 0, NULL} +}; + +int +python_init_document_interface(PyObject *dict, PyObject *name) +{ + return add_python_methods(dict, name, document_methods); +} diff --git a/src/scripting/python/keybinding.h b/src/scripting/python/keybinding.h new file mode 100644 index 0000000..e0c73a1 --- /dev/null +++ b/src/scripting/python/keybinding.h @@ -0,0 +1,9 @@ +#ifndef EL__SCRIPTING_PYTHON_KEYBINDING_H +#define EL__SCRIPTING_PYTHON_KEYBINDING_H + +#include + +int python_init_keybinding_interface(PyObject *dict, PyObject *name); +void python_done_keybinding_interface(void); + +#endif diff --git a/src/scripting/python/keybinding.c b/src/scripting/python/keybinding.c new file mode 100644 index 0000000..25a417e --- /dev/null +++ b/src/scripting/python/keybinding.c @@ -0,0 +1,198 @@ +/* Keystroke bindings for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "elinks.h" + +#include "config/kbdbind.h" +#include "intl/gettext/libintl.h" +#include "main/event.h" +#include "scripting/python/core.h" +#include "session/session.h" +#include "util/error.h" +#include "util/string.h" + +static PyObject *keybindings = NULL; + +/* C wrapper that invokes Python callbacks for bind_key_to_event_name(). */ + +static enum evhook_status +invoke_keybinding_callback(va_list ap, void *data) +{ + PyObject *callback = data; + struct session *saved_python_ses = python_ses; + PyObject *result; + + python_ses = va_arg(ap, struct session *); + + result = PyObject_CallFunction(callback, NULL); + if (result) + Py_DECREF(result); + else + alert_python_error(); + + python_ses = saved_python_ses; + + return EVENT_HOOK_STATUS_NEXT; +} + +/* Check that a keymap name is valid. */ + +static int +keymap_is_valid(const unsigned char *keymap) +{ + enum keymap_id keymap_id; + + for (keymap_id = 0; keymap_id < KEYMAP_MAX; ++keymap_id) + if (!strcmp(keymap, get_keymap_name(keymap_id))) + break; + return (keymap_id != KEYMAP_MAX); +} + +/* Python interface for binding keystrokes to callable objects. */ + +static char python_bind_key_doc[] = \ +"bind_key(keystroke, callback[, keymap]) -> None\n\ +\n\ +Bind a keystroke to a callable object.\n\ +\n\ +Arguments:\n\ +\n\ +keystroke -- A string containing a keystroke. The syntax for\n\ + keystrokes is described in the elinkskeys(5) man page.\n\ +callback -- A callable object to be called when the keystroke is\n\ + typed. It will be called without any arguments.\n\ +\n\ +Optional arguments:\n\ +\n\ +keymap -- A string containing the name of a keymap. Valid keymap\n\ + names can be found in the elinkskeys(5) man page. By\n\ + default the \"main\" keymap is used.\n"; + +static PyObject * +python_bind_key(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const unsigned char *keystroke; + PyObject *callback; + unsigned char *keymap = "main"; + PyObject *key_tuple; + PyObject *old_callback; + struct string event_name; + int event_id; + unsigned char *error_msg; + static char *kwlist[] = {"keystroke", "callback", "keymap", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|s:bind_key", kwlist, + &keystroke, &callback, &keymap)) + return NULL; + + assert(keystroke && callback && keymap); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + if (!keymap_is_valid(keymap)) { + PyErr_Format(python_elinks_err, "%s \"%s\"", + N_("Unrecognised keymap"), keymap); + return NULL; + } + + /* + * The callback object needs to be kept alive for as long as the + * keystroke is bound, so we stash a reference to it in a dictionary. + * We don't need to use the dictionary to find callbacks; its sole + * purpose is to prevent these objects from being garbage-collected + * by the Python interpreter. + * + * If binding the key fails for any reason after this point then + * we'll need to restore the dictionary to its previous state, which + * is temporarily preserved in @old_callback. + */ + key_tuple = Py_BuildValue("ss", keymap, keystroke); + if (!key_tuple) + return NULL; + old_callback = PyDict_GetItem(keybindings, key_tuple); + Py_XINCREF(old_callback); + if (PyDict_SetItem(keybindings, key_tuple, callback) != 0) { + Py_DECREF(key_tuple); + Py_XDECREF(old_callback); + return NULL; + } + + if (!init_string(&event_name)) { + PyErr_NoMemory(); + goto rollback; + } + if (!add_format_to_string(&event_name, "python-func %p", callback)) { + PyErr_SetFromErrno(python_elinks_err); + done_string(&event_name); + goto rollback; + } + event_id = bind_key_to_event_name(keymap, keystroke, event_name.source, + &error_msg); + done_string(&event_name); + if (error_msg) { + PyErr_SetString(python_elinks_err, error_msg); + goto rollback; + } + + event_id = register_event_hook(event_id, invoke_keybinding_callback, 0, + callback); + if (event_id == EVENT_NONE) { + PyErr_SetString(python_elinks_err, + N_("Error registering event hook")); + goto rollback; + } + + Py_DECREF(key_tuple); + Py_XDECREF(old_callback); + + Py_INCREF(Py_None); + return Py_None; + +rollback: + /* + * If an error occurred, try to restore the keybindings dictionary + * to its previous state. + */ + if (old_callback) { + (void) PyDict_SetItem(keybindings, key_tuple, old_callback); + Py_DECREF(old_callback); + } else { + (void) PyDict_DelItem(keybindings, key_tuple); + } + + Py_DECREF(key_tuple); + return NULL; +} + +static PyMethodDef keybinding_methods[] = { + {"bind_key", (PyCFunction) python_bind_key, + METH_VARARGS | METH_KEYWORDS, + python_bind_key_doc}, + + {NULL, NULL, 0, NULL} +}; + +int +python_init_keybinding_interface(PyObject *dict, PyObject *name) +{ + keybindings = PyDict_New(); + if (!keybindings) return -1; + + return add_python_methods(dict, name, keybinding_methods); +} + +void +python_done_keybinding_interface(void) +{ + Py_XDECREF(keybindings); +} diff --git a/src/scripting/python/load.h b/src/scripting/python/load.h new file mode 100644 index 0000000..3050543 --- /dev/null +++ b/src/scripting/python/load.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_PYTHON_LOAD_H +#define EL__SCRIPTING_PYTHON_LOAD_H + +#include + +int python_init_load_interface(PyObject *dict, PyObject *name); + +#endif diff --git a/src/scripting/python/load.c b/src/scripting/python/load.c new file mode 100644 index 0000000..32f454f --- /dev/null +++ b/src/scripting/python/load.c @@ -0,0 +1,167 @@ +/* Document loading for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "cache/cache.h" +#include "intl/gettext/libintl.h" +#include "network/connection.h" +#include "network/state.h" +#include "protocol/uri.h" +#include "scripting/python/core.h" +#include "session/download.h" +#include "session/session.h" +#include "session/task.h" +#include "util/error.h" +#include "util/memory.h" + +struct python_load_uri_callback_hop { + struct session *ses; + PyObject *callback; +}; + +/* C wrapper that invokes Python callbacks for load_uri(). */ + +static void +invoke_load_uri_callback(struct download *download, void *data) +{ + struct python_load_uri_callback_hop *hop = data; + struct session *saved_python_ses = python_ses; + + assert(download); + if_assert_failed { + if (hop && hop->callback) { + Py_DECREF(hop->callback); + } + mem_free_if(hop); + return; + } + + if (is_in_progress_state(download->state)) return; + + assert(hop && hop->callback); + if_assert_failed { + mem_free(download); + mem_free_if(hop); + return; + } + + if (download->cached) { + PyObject *result; + struct fragment *f = download->cached->frag.next; + + python_ses = hop->ses; + + result = PyObject_CallFunction(hop->callback, "ss#", + download->cached->head, + f ? f->data : NULL, + f ? f->length : 0); + if (result) + Py_DECREF(result); + else + alert_python_error(); + } + + Py_DECREF(hop->callback); + mem_free(hop); + mem_free(download); + + python_ses = saved_python_ses; +} + +/* Python interface for loading a document. */ + +static char python_load_doc[] = \ +"load(url, callback) -> None\n\ +\n\ +Load a document into the ELinks cache and pass its contents to a\n\ +callable object.\n\ +\n\ +Arguments:\n\ +\n\ +url -- A string containing the URL to load.\n\ +callback -- A callable object to be called after the document has\n\ + been loaded. It will be called with two arguments: the first\n\ + will be a string representing the document's header, or None\n\ + if it has no header; the second will be a string representing\n\ + the document's body, or None if it has no body.\n"; + +static PyObject * +python_load(PyObject *self, PyObject *args) +{ + unsigned char *uristring; + PyObject *callback; + struct uri *uri; + struct download *download; + struct python_load_uri_callback_hop *hop; + + if (!python_ses) { + PyErr_SetString(python_elinks_err, "No session"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "sO:load", &uristring, &callback)) + return NULL; + + assert(uristring && callback); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + uri = get_translated_uri(uristring, python_ses->tab->term->cwd); + if (!uri) { + PyErr_SetString(python_elinks_err, N_("Bad URL syntax")); + return NULL; + } + + download = mem_alloc(sizeof(*download)); + if (!download) goto mem_error; + + hop = mem_alloc(sizeof(*hop)); + if (!hop) goto free_download; + hop->ses = python_ses; + hop->callback = callback; + Py_INCREF(callback); + + download->data = hop; + download->callback = (download_callback_T *) invoke_load_uri_callback; + if (load_uri(uri, NULL, download, PRI_MAIN, CACHE_MODE_NORMAL, -1) != 0) { + PyErr_SetString(python_elinks_err, + get_state_message(download->state, + python_ses->tab->term)); + done_uri(uri); + return NULL; + } + + done_uri(uri); + Py_INCREF(Py_None); + return Py_None; + +free_download: + mem_free(download); + +mem_error: + done_uri(uri); + return PyErr_NoMemory(); +} + + +static PyMethodDef load_methods[] = { + {"load", python_load, + METH_VARARGS, + python_load_doc}, + + {NULL, NULL, 0, NULL} +}; + +int +python_init_load_interface(PyObject *dict, PyObject *name) +{ + return add_python_methods(dict, name, load_methods); +} diff --git a/src/scripting/python/menu.h b/src/scripting/python/menu.h new file mode 100644 index 0000000..013047d --- /dev/null +++ b/src/scripting/python/menu.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_PYTHON_MENU_H +#define EL__SCRIPTING_PYTHON_MENU_H + +#include + +int python_init_menu_interface(PyObject *dict, PyObject *name); + +#endif diff --git a/src/scripting/python/menu.c b/src/scripting/python/menu.c new file mode 100644 index 0000000..fb0bffc --- /dev/null +++ b/src/scripting/python/menu.c @@ -0,0 +1,241 @@ +/* Simple menus for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "bfu/menu.h" +#include "document/document.h" +#include "document/view.h" +#include "intl/gettext/libintl.h" +#include "scripting/python/core.h" +#include "session/session.h" +#include "terminal/window.h" +#include "util/error.h" +#include "util/memlist.h" +#include "util/memory.h" +#include "util/string.h" +#include "viewer/text/view.h" + +/* C wrapper that invokes Python callbacks for menu items. */ + +static void +invoke_menu_callback(struct terminal *term, void *data, void *ses) +{ + PyObject *callback = data; + struct session *saved_python_ses = python_ses; + PyObject *result; + + python_ses = ses; + + result = PyObject_CallFunction(callback, NULL); + if (result) + Py_DECREF(result); + else + alert_python_error(); + + Py_DECREF(callback); + + python_ses = saved_python_ses; +} + +enum python_menu_type { + PYTHON_MENU_DEFAULT, + PYTHON_MENU_LINK, + PYTHON_MENU_TAB, + PYTHON_MENU_MAX +}; + +/* Python interface for displaying simple menus. */ + +static char python_menu_doc[] = \ +"menu(items[, type]) -> None\n\ +\n\ +Display a menu.\n\ +\n\ +Arguments:\n\ +\n\ +items -- A sequence of tuples. Each tuple must have two elements: a\n\ + string containing the name of a menu item, and a callable\n\ + object that will be called without any arguments if the user\n\ + selects that menu item.\n\ +\n\ +Optional arguments:\n\ +\n\ +type -- A constant specifying the type of menu to display. By default\n\ + the menu is displayed at the top of the screen, but if this\n\ + argument's value is the constant elinks.MENU_TAB then the menu\n\ + is displayed in the same location as the ELinks tab menu. If\n\ + its value is the constant elinks.MENU_LINK then the menu is\n\ + displayed in the same location as the ELinks link menu and is\n\ + not displayed unless a link is currently selected.\n"; + +static PyObject * +python_menu(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *items; + enum python_menu_type menu_type = PYTHON_MENU_DEFAULT; + int length, i; + struct menu_item *menu; + struct memory_list *ml = NULL; + static char *kwlist[] = {"items", "type", NULL}; + + if (!python_ses) { + PyErr_SetString(python_elinks_err, "No session"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i:menu", + kwlist, &items, &menu_type)) + return NULL; + + assert(items); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + if (!PySequence_Check(items)) { + PyErr_SetString(PyExc_TypeError, "Argument must be a sequence"); + return NULL; + } + length = PySequence_Length(items); + if (length == -1) return NULL; + else if (length == 0) goto success; + + if (menu_type < 0 || menu_type >= PYTHON_MENU_MAX) { + PyErr_Format(python_elinks_err, "%s %d", + N_("Bad number"), menu_type); + return NULL; + + } else if (menu_type == PYTHON_MENU_LINK) { + if (!get_current_link(current_frame(python_ses))) + goto success; + + } else if (menu_type == PYTHON_MENU_TAB + && python_ses->status.show_tabs_bar) { + int y; + + if (python_ses->status.show_tabs_bar_at_top) + y = python_ses->status.show_title_bar; + else + y = python_ses->tab->term->height - length + - python_ses->status.show_status_bar - 2; + + set_window_ptr(python_ses->tab, python_ses->tab->xpos, + int_max(y, 0)); + + } else { + set_window_ptr(python_ses->tab, 0, 0); + } + + menu = new_menu(FREE_LIST | FREE_TEXT | NO_INTL); + if (!menu) return PyErr_NoMemory(); + + /* + * Keep track of all the memory we allocate so we'll be able to free + * it in case any error prevents us from displaying the menu. + */ + ml = getml(menu, NULL); + if (!ml) { + mem_free(menu); + return PyErr_NoMemory(); + } + + for (i = 0; i < length; i++) { + PyObject *tuple = PySequence_GetItem(items, i); + PyObject *name, *callback; + unsigned char *contents; + + if (!tuple) goto error; + + if (!PyTuple_Check(tuple)) { + Py_DECREF(tuple); + PyErr_SetString(PyExc_TypeError, + "Argument must be sequence of tuples"); + goto error; + } + name = PyTuple_GetItem(tuple, 0); + callback = PyTuple_GetItem(tuple, 1); + Py_DECREF(tuple); + if (!name || !callback) goto error; + + contents = (unsigned char *) PyString_AsString(name); + if (!contents) goto error; + + contents = stracpy(contents); + if (!contents) { + PyErr_NoMemory(); + goto error; + } + add_one_to_ml(&ml, contents); + + /* + * FIXME: We need to increment the reference counts for + * callbacks so they won't be garbage-collected by the Python + * interpreter before they're called. But for any callback + * that isn't called (because the user doesn't select the + * corresponding menu item) we'll never have an opportunity + * to decrement the reference count again, so this code leaks + * references. It probably can't be fixed without changes to + * the menu machinery in bfu/menu.c, e.g. to call an arbitrary + * clean-up function when a menu is destroyed. + * + * The good news is that in a typical usage case, where the + * callback objects wouldn't be garbage-collected anyway until + * the Python interpreter exits, this makes no difference at + * all. But it's not strictly correct, and it could leak memory + * in more elaborate usage where callback objects are created + * and thrown away on the fly. + */ + Py_INCREF(callback); + add_to_menu(&menu, contents, NULL, ACT_MAIN_NONE, + invoke_menu_callback, callback, 0); + } + + do_menu(python_ses->tab->term, menu, python_ses, 1); + +success: + mem_free_if(ml); + + Py_INCREF(Py_None); + return Py_None; + +error: + freeml(ml); + return NULL; +} + +static PyMethodDef menu_methods[] = { + {"menu", (PyCFunction) python_menu, + METH_VARARGS | METH_KEYWORDS, + python_menu_doc}, + + {NULL, NULL, 0, NULL} +}; + +static int +add_constant(PyObject *dict, const char *key, int value) +{ + PyObject *constant = PyInt_FromLong(value); + int result; + + if (!constant) return -1; + result = PyDict_SetItemString(dict, key, constant); + Py_DECREF(constant); + + return result; +} + +int +python_init_menu_interface(PyObject *dict, PyObject *name) +{ + if (add_constant(dict, "MENU_LINK", PYTHON_MENU_LINK) != 0) return -1; + if (add_constant(dict, "MENU_TAB", PYTHON_MENU_TAB) != 0) return -1; + + return add_python_methods(dict, name, menu_methods); +} diff --git a/src/scripting/python/open.h b/src/scripting/python/open.h new file mode 100644 index 0000000..bbcf5da --- /dev/null +++ b/src/scripting/python/open.h @@ -0,0 +1,8 @@ +#ifndef EL__SCRIPTING_PYTHON_OPEN_H +#define EL__SCRIPTING_PYTHON_OPEN_H + +#include + +int python_init_open_interface(PyObject *dict, PyObject *name); + +#endif diff --git a/src/scripting/python/open.c b/src/scripting/python/open.c new file mode 100644 index 0000000..2838f11 --- /dev/null +++ b/src/scripting/python/open.c @@ -0,0 +1,92 @@ +/* Document viewing for Python. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "elinks.h" + +#include "intl/gettext/libintl.h" +#include "protocol/uri.h" +#include "scripting/python/core.h" +#include "session/task.h" +#include "terminal/tab.h" +#include "util/error.h" + +/* Python interface for viewing a document. */ + +static char python_open_doc[] = \ +"open(url, new_tab=False, background=False) -> None\n\ +\n\ +View a document in either the current tab or a new tab.\n\ +\n\ +Arguments:\n\ +\n\ +url -- A string containing the URL to view.\n\ +\n\ +Optional keyword arguments:\n\ +\n\ +new_tab -- By default the URL is opened in the current tab. If this\n\ + argument's value is the boolean True then the URL is instead\n\ + opened in a new tab.\n\ +background -- By default a new tab is opened in the foreground. If\n\ + this argument's value is the boolean True then a new tab is\n\ + instead opened in the background. This argument is ignored\n\ + unless new_tab's value is True.\n"; + +static PyObject * +python_open(PyObject *self, PyObject *args, PyObject *kwargs) +{ + unsigned char *url; + int new_tab = 0, background = 0; + struct uri *uri; + static char *kwlist[] = {"url", "new_tab", "background", NULL}; + + if (!python_ses) { + PyErr_SetString(python_elinks_err, "No session"); + return NULL; + } + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|ii:open", + kwlist, &url, + &new_tab, &background)) + return NULL; + + assert(url); + if_assert_failed { + PyErr_SetString(python_elinks_err, N_("Internal error")); + return NULL; + } + + uri = get_translated_uri(url, python_ses->tab->term->cwd); + if (!uri) { + PyErr_SetString(python_elinks_err, N_("Bad URL syntax")); + return NULL; + } + + if (new_tab) + open_uri_in_new_tab(python_ses, uri, background, 0); + else + goto_uri(python_ses, uri); + + done_uri(uri); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef open_methods[] = { + {"open", (PyCFunction) python_open, + METH_VARARGS | METH_KEYWORDS, + python_open_doc}, + + {NULL, NULL, 0, NULL} +}; + +int +python_init_open_interface(PyObject *dict, PyObject *name) +{ + return add_python_methods(dict, name, open_methods); +} diff --git a/src/scripting/python/python.c b/src/scripting/python/python.c index cf9e0c0..13411c4 100644 --- a/src/scripting/python/python.c +++ b/src/scripting/python/python.c @@ -4,11 +4,10 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#include "scripting/python/core.h" - #include "elinks.h" #include "main/module.h" +#include "scripting/python/core.h" #include "scripting/python/hooks.h" diff --git a/src/scripting/python/core.h b/src/scripting/python/core.h index 4450313..1acce19 100644 --- a/src/scripting/python/core.h +++ b/src/scripting/python/core.h @@ -2,11 +2,18 @@ #ifndef EL__SCRIPTING_PYTHON_CORE_H #define EL__SCRIPTING_PYTHON_CORE_H +#include + struct module; -struct session; -void alert_python_error(struct session *ses); +extern struct session *python_ses; +extern PyObject *python_elinks_err; + +void alert_python_error(void); + void init_python(struct module *module); void cleanup_python(struct module *module); +int add_python_methods(PyObject *dict, PyObject *name, PyMethodDef *methods); + #endif diff --git a/src/scripting/python/core.c b/src/scripting/python/core.c index 83eb2c3..a3c6e58 100644 --- a/src/scripting/python/core.c +++ b/src/scripting/python/core.c @@ -5,37 +5,42 @@ #include "config.h" #endif #include +#include -#include #include #include "elinks.h" #include "config/home.h" #include "main/module.h" -#include "scripting/scripting.h" #include "scripting/python/core.h" +#include "scripting/python/dialogs.h" +#include "scripting/python/document.h" +#include "scripting/python/keybinding.h" +#include "scripting/python/load.h" +#include "scripting/python/menu.h" +#include "scripting/python/open.h" #include "scripting/python/python.h" +#include "scripting/scripting.h" +#include "session/session.h" #include "util/env.h" -#include "util/file.h" #include "util/string.h" +struct session *python_ses = NULL; -PyObject *pDict = NULL, *pModule = NULL; +PyObject *python_elinks_err = NULL; +PyObject *python_hooks = NULL; /* Error reporting. */ void -alert_python_error(struct session *ses) +alert_python_error(void) { unsigned char *msg = "(no traceback available)"; PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL; PyObject *tb_module = NULL; - PyObject *tb_dict; - PyObject *format_function; PyObject *msg_list = NULL; PyObject *empty_string = NULL; - PyObject *join_method = NULL; PyObject *msg_string = NULL; unsigned char *temp; @@ -46,17 +51,15 @@ alert_python_error(struct session *ses) */ PyErr_Fetch(&err_type, &err_value, &err_traceback); PyErr_NormalizeException(&err_type, &err_value, &err_traceback); - if (!err_traceback) goto end; + if (!err_type) goto end; tb_module = PyImport_ImportModule("traceback"); if (!tb_module) goto end; - tb_dict = PyModule_GetDict(tb_module); - format_function = PyDict_GetItemString(tb_dict, "format_exception"); - if (!format_function || !PyCallable_Check(format_function)) goto end; - - msg_list = PyObject_CallFunction(format_function, "OOO", - err_type, err_value, err_traceback); + msg_list = PyObject_CallMethod(tb_module, "format_exception", "OOO", + err_type, + err_value ? err_value : Py_None, + err_traceback ? err_traceback : Py_None); if (!msg_list) goto end; /* @@ -67,17 +70,14 @@ alert_python_error(struct session *ses) empty_string = PyString_FromString(""); if (!empty_string) goto end; - join_method = PyObject_GetAttrString(empty_string, "join"); - if (!join_method || !PyCallable_Check(join_method)) goto end; - - msg_string = PyObject_CallFunction(join_method, "O", msg_list); + msg_string = PyObject_CallMethod(empty_string, "join", "O", msg_list); if (!msg_string) goto end; - temp = (unsigned char *)PyString_AsString(msg_string); + temp = (unsigned char *) PyString_AsString(msg_string); if (temp) msg = temp; end: - report_scripting_error(&python_scripting_module, ses, msg); + report_scripting_error(&python_scripting_module, python_ses, msg); Py_XDECREF(err_type); Py_XDECREF(err_value); @@ -85,31 +85,69 @@ end: Py_XDECREF(tb_module); Py_XDECREF(msg_list); Py_XDECREF(empty_string); - Py_XDECREF(join_method); Py_XDECREF(msg_string); /* In case another error occurred while reporting the original error: */ PyErr_Clear(); } -void -cleanup_python(struct module *module) -{ - if (Py_IsInitialized()) { - Py_XDECREF(pDict); - Py_XDECREF(pModule); - Py_Finalize(); - } +/* Prepend ELinks directories to Python's search path. */ + +static int +set_python_search_path(void) +{ + struct string new_python_path, *okay; + unsigned char *old_python_path; + int result = -1; + + if (!init_string(&new_python_path)) return result; + + old_python_path = (unsigned char *) getenv("PYTHONPATH"); + if (old_python_path) + okay = add_format_to_string(&new_python_path, "%s%c%s%c%s", + elinks_home, DELIM, CONFDIR, + DELIM, old_python_path); + else + okay = add_format_to_string(&new_python_path, "%s%c%s", + elinks_home, DELIM, CONFDIR); + if (okay) result = env_set("PYTHONPATH", new_python_path.source, -1); + done_string(&new_python_path); + return result; } +/* Module-level documentation for the Python interpreter's elinks module. */ + +static char module_doc[] = \ +"Interface to the ELinks web browser.\n\ +\n\ +Functions:\n\ +\n\ +bind_key() -- Bind a keystroke to a callable object.\n\ +current_document() -- Return the body of the document being viewed.\n\ +current_header() -- Return the header of the document being viewed.\n\ +current_link_url() -- Return the URL of the currently selected link.\n\ +current_title() -- Return the title of the document being viewed.\n\ +current_url() -- Return the URL of the document being viewed.\n\ +info_box() -- Display information to the user.\n\ +input_box() -- Prompt for user input.\n\ +load() -- Load a document into the ELinks cache.\n\ +menu() -- Display a menu.\n\ +open() -- View a document.\n\ +\n\ +Exception classes:\n\ +\n\ +error -- Errors internal to ELinks.\n\ +\n\ +Other public objects:\n\ +\n\ +home -- A string containing the pathname of the ~/.elinks directory.\n"; + void init_python(struct module *module) { - unsigned char *python_path = straconcat(elinks_home, ":", CONFDIR, NULL); + PyObject *elinks_module, *module_dict, *module_name; - if (!python_path) return; - env_set("PYTHONPATH", python_path, -1); - mem_free(python_path); + if (set_python_search_path() != 0) return; /* Treat warnings as errors so they can be caught and handled; * otherwise they would be printed to stderr. @@ -123,12 +161,66 @@ init_python(struct module *module) PySys_AddWarnOption("error"); Py_Initialize(); - pModule = PyImport_ImportModule("hooks"); - if (pModule) { - pDict = PyModule_GetDict(pModule); - Py_INCREF(pDict); - } else { - alert_python_error(NULL); + elinks_module = Py_InitModule3("elinks", NULL, module_doc); + if (!elinks_module) goto python_error; + + if (PyModule_AddStringConstant(elinks_module, "home", elinks_home) != 0) + goto python_error; + + python_elinks_err = PyErr_NewException("elinks.error", NULL, NULL); + if (!python_elinks_err) goto python_error; + + if (PyModule_AddObject(elinks_module, "error", python_elinks_err) != 0) + goto python_error; + + module_dict = PyModule_GetDict(elinks_module); + if (!module_dict) goto python_error; + module_name = PyString_FromString("elinks"); + if (!module_name) goto python_error; + + if (python_init_dialogs_interface(module_dict, module_name) != 0 + || python_init_document_interface(module_dict, module_name) != 0 + || python_init_keybinding_interface(module_dict, module_name) != 0 + || python_init_load_interface(module_dict, module_name) != 0 + || python_init_menu_interface(module_dict, module_name) != 0 + || python_init_open_interface(module_dict, module_name) != 0) + goto python_error; + + python_hooks = PyImport_ImportModule("hooks"); + if (!python_hooks) goto python_error; + + return; + +python_error: + alert_python_error(); +} + +void +cleanup_python(struct module *module) +{ + if (Py_IsInitialized()) { + python_done_keybinding_interface(); + Py_XDECREF(python_hooks); + Py_Finalize(); + } +} + +/* Add methods to a Python extension module. */ + +int +add_python_methods(PyObject *dict, PyObject *name, PyMethodDef *methods) +{ + PyMethodDef *method; + + for (method = methods; method && method->ml_name; method++) { + PyObject *function = PyCFunction_NewEx(method, NULL, name); + int result; + + if (!function) return -1; + result = PyDict_SetItemString(dict, method->ml_name, function); + Py_DECREF(function); + if (result != 0) return -1; } + return 0; } diff --git a/src/scripting/python/hooks.c b/src/scripting/python/hooks.c index af9b618..23a07f0 100644 --- src/scripting/python/hooks.c +++ src/scripting/python/hooks.c @@ -1,225 +1,201 @@ -/* Python scripting hooks */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - - -#include "elinks.h" - -#include "cache/cache.h" -#include "main/event.h" -#include "protocol/uri.h" -#include "scripting/python/core.h" -#include "scripting/python/hooks.h" -#include "session/location.h" -#include "session/session.h" -#include "util/string.h" - -#undef _POSIX_C_SOURCE -#include - -/* The events that will trigger the functions below and what they are expected - * to do is explained in doc/events.txt */ - -extern PyObject *pDict; -extern PyObject *pModule; - -static void -do_script_hook_goto_url(struct session *ses, unsigned char **url) -{ - PyObject *pFunc = PyDict_GetItemString(pDict, "goto_url_hook"); - - if (pFunc && PyCallable_Check(pFunc)) { - PyObject *pValue; - unsigned char *current_url; - - if (!ses || !have_location(ses)) { - current_url = NULL; - } else { - current_url = struri(cur_loc(ses)->vs.uri); - } - - pValue = PyObject_CallFunction(pFunc, "ss", *url, current_url); - if (pValue) { - if (pValue != Py_None) { - const unsigned char *str; - unsigned char *new_url; - - str = PyString_AsString(pValue); - if (str) { - new_url = stracpy((unsigned char *)str); - if (new_url) mem_free_set(url, new_url); - } - } - Py_DECREF(pValue); - } else { - alert_python_error(ses); - } - } -} - -static enum evhook_status -script_hook_goto_url(va_list ap, void *data) -{ - unsigned char **url = va_arg(ap, unsigned char **); - struct session *ses = va_arg(ap, struct session *); - - if (pDict && *url) - do_script_hook_goto_url(ses, url); - - return EVENT_HOOK_STATUS_NEXT; -} - -static void -do_script_hook_follow_url(struct session *ses, unsigned char **url) -{ - PyObject *pFunc = PyDict_GetItemString(pDict, "follow_url_hook"); - - if (pFunc && PyCallable_Check(pFunc)) { - PyObject *pValue = PyObject_CallFunction(pFunc, "s", *url); - if (pValue) { - if (pValue != Py_None) { - const unsigned char *str; - unsigned char *new_url; - - str = PyString_AsString(pValue); - if (str) { - new_url = stracpy((unsigned char *)str); - if (new_url) mem_free_set(url, new_url); - } - } - Py_DECREF(pValue); - } else { - alert_python_error(ses); - } - } -} - -static enum evhook_status -script_hook_follow_url(va_list ap, void *data) -{ - unsigned char **url = va_arg(ap, unsigned char **); - struct session *ses = va_arg(ap, struct session *); - - if (pDict && *url) - do_script_hook_follow_url(ses, url); - - return EVENT_HOOK_STATUS_NEXT; -} - -static void -do_script_hook_pre_format_html(struct session *ses, unsigned char *url, - struct cache_entry *cached, - struct fragment *fragment) -{ - PyObject *pFunc = PyDict_GetItemString(pDict, "pre_format_html_hook"); - - if (pFunc && PyCallable_Check(pFunc)) { - PyObject *pValue = PyObject_CallFunction(pFunc, "ss#", url, - fragment->data, - fragment->length); - - if (pValue) { - if (pValue != Py_None) { - const unsigned char *str; - int len; - - str = PyString_AsString(pValue); - if (str) { - len = PyString_Size(pValue); - add_fragment(cached, 0, str, len); - normalize_cache_entry(cached, len); - } - } - Py_DECREF(pValue); - } else { - alert_python_error(ses); - } - } -} - -static enum evhook_status -script_hook_pre_format_html(va_list ap, void *data) -{ - struct session *ses = va_arg(ap, struct session *); - struct cache_entry *cached = va_arg(ap, struct cache_entry *); - struct fragment *fragment = get_cache_fragment(cached); - unsigned char *url = struri(cached->uri); - - if (pDict && ses && url && cached->length && *fragment->data) - do_script_hook_pre_format_html(ses, url, cached, fragment); - - return EVENT_HOOK_STATUS_NEXT; -} - -static inline void -do_script_hook_get_proxy(unsigned char **new_proxy_url, unsigned char *url) -{ - PyObject *pFunc = PyDict_GetItemString(pDict, "proxy_for_hook"); - - if (pFunc && PyCallable_Check(pFunc)) { - PyObject *pValue = PyObject_CallFunction(pFunc, "s", url); - - if (pValue) { - if (pValue != Py_None) { - const unsigned char *str; - unsigned char *new_url; - - str = PyString_AsString(pValue); - if (str) { - new_url = stracpy((unsigned char *)str); - if (new_url) mem_free_set(new_proxy_url, - new_url); - } - } - Py_DECREF(pValue); - } else { - alert_python_error(NULL); - } - } -} - -static enum evhook_status -script_hook_get_proxy(va_list ap, void *data) -{ - unsigned char **new_proxy_url = va_arg(ap, unsigned char **); - unsigned char *url = va_arg(ap, unsigned char *); - - if (pDict && new_proxy_url && url) - do_script_hook_get_proxy(new_proxy_url, url); - - return EVENT_HOOK_STATUS_NEXT; -} - -static void -do_script_hook_quit(void) -{ - PyObject *pFunc = PyDict_GetItemString(pDict, "quit_hook"); - - if (pFunc && PyCallable_Check(pFunc)) { - PyObject *pValue = PyObject_CallFunction(pFunc, NULL); - - if (pValue) { - Py_DECREF(pValue); - } else { - alert_python_error(NULL); - } - } -} - -static enum evhook_status -script_hook_quit(va_list ap, void *data) -{ - if (pDict) do_script_hook_quit(); - return EVENT_HOOK_STATUS_NEXT; -} - -struct event_hook_info python_scripting_hooks[] = { - { "goto-url", 0, script_hook_goto_url, NULL }, - { "follow-url", 0, script_hook_follow_url, NULL }, - { "pre-format-html", 0, script_hook_pre_format_html, NULL }, - { "get-proxy", 0, script_hook_get_proxy, NULL }, - { "quit", 0, script_hook_quit, NULL }, - NULL_EVENT_HOOK_INFO, -}; +/* Python scripting hooks */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include + +#include "elinks.h" + +#include "cache/cache.h" +#include "main/event.h" +#include "protocol/uri.h" +#include "scripting/python/core.h" +#include "session/location.h" +#include "session/session.h" +#include "util/memory.h" +#include "util/string.h" + +extern PyObject *python_hooks; + +/* + * A utility function for script_hook_url() and script_hook_get_proxy(): + * Free a char * and replace it with the contents of a Python string. + * (Py_None is ignored.) + */ + +static PyObject * +replace_with_python_string(unsigned char **dest, PyObject *object) +{ + unsigned char *str; + + if (object == Py_None) return object; + + str = (unsigned char *) PyString_AsString(object); + if (!str) return NULL; + + str = stracpy(str); + if (!str) return PyErr_NoMemory(); + + mem_free_set(dest, str); + return object; +} + +/* Call a Python hook for a goto-url or follow-url event. */ + +static enum evhook_status +script_hook_url(va_list ap, void *data) +{ + unsigned char **url = va_arg(ap, unsigned char **); + struct session *ses = va_arg(ap, struct session *); + char *method = data; + struct session *saved_python_ses = python_ses; + PyObject *result; + + evhook_use_params(url && ses); + + if (!python_hooks || !url || !*url + || !PyObject_HasAttrString(python_hooks, method)) + return EVENT_HOOK_STATUS_NEXT; + + python_ses = ses; + + /* + * Historical note: The only reason the goto and follow hooks are + * treated differently is to maintain backwards compatibility for + * people who already have a goto_url_hook() function in hooks.py + * that expects a second argument. If we were starting over from + * scratch, we could treat the goto and follow hooks identically and + * simply pass @url as the sole argument in both cases; the Python + * code for the goto hook no longer needs its @current_url argument + * since it could instead determine the current URL by calling the + * Python interpreter's elinks.current_url() function. + */ + if (!strcmp(method, "goto_url_hook")) { + unsigned char *current_url = NULL; + + if (python_ses && have_location(python_ses)) + current_url = struri(cur_loc(ses)->vs.uri); + + result = PyObject_CallMethod(python_hooks, method, "ss", *url, + current_url); + } else { + result = PyObject_CallMethod(python_hooks, method, "s", *url); + } + + if (!result || !replace_with_python_string(url, result)) + alert_python_error(); + + Py_XDECREF(result); + + python_ses = saved_python_ses; + + return EVENT_HOOK_STATUS_NEXT; +} + +/* Call a Python hook for a pre-format-html event. */ + +static enum evhook_status +script_hook_pre_format_html(va_list ap, void *data) +{ + struct session *ses = va_arg(ap, struct session *); + struct cache_entry *cached = va_arg(ap, struct cache_entry *); + struct fragment *fragment = get_cache_fragment(cached); + unsigned char *url = struri(cached->uri); + char *method = "pre_format_html_hook"; + struct session *saved_python_ses = python_ses; + PyObject *result; + int success = 0; + + evhook_use_params(ses && cached); + + if (!python_hooks || !cached->length || !*fragment->data + || !PyObject_HasAttrString(python_hooks, method)) + return EVENT_HOOK_STATUS_NEXT; + + python_ses = ses; + + result = PyObject_CallMethod(python_hooks, method, "ss#", url, + fragment->data, fragment->length); + if (!result) goto error; + + if (result != Py_None) { + unsigned char *str; + int len; + + if (PyString_AsStringAndSize(result, (char **) &str, &len) != 0) + goto error; + + (void) add_fragment(cached, 0, str, len); + normalize_cache_entry(cached, len); + } + + success = 1; + +error: + if (!success) alert_python_error(); + + Py_XDECREF(result); + + python_ses = saved_python_ses; + + return EVENT_HOOK_STATUS_NEXT; +} + +/* Call a Python hook for a get-proxy event. */ + +static enum evhook_status +script_hook_get_proxy(va_list ap, void *data) +{ + unsigned char **proxy = va_arg(ap, unsigned char **); + unsigned char *url = va_arg(ap, unsigned char *); + char *method = "proxy_for_hook"; + PyObject *result; + + evhook_use_params(proxy && url); + + if (!python_hooks || !proxy || !url + || !PyObject_HasAttrString(python_hooks, method)) + return EVENT_HOOK_STATUS_NEXT; + + result = PyObject_CallMethod(python_hooks, method, "s", url); + + if (!result || !replace_with_python_string(proxy, result)) + alert_python_error(); + + Py_XDECREF(result); + + return EVENT_HOOK_STATUS_NEXT; +} + +/* Call a Python hook for a quit event. */ + +static enum evhook_status +script_hook_quit(va_list ap, void *data) +{ + char *method = "quit_hook"; + PyObject *result; + + if (!python_hooks || !PyObject_HasAttrString(python_hooks, method)) + return EVENT_HOOK_STATUS_NEXT; + + result = PyObject_CallMethod(python_hooks, method, NULL); + if (!result) alert_python_error(); + + Py_XDECREF(result); + + return EVENT_HOOK_STATUS_NEXT; +} + +struct event_hook_info python_scripting_hooks[] = { + { "goto-url", 0, script_hook_url, "goto_url_hook" }, + { "follow-url", 0, script_hook_url, "follow_url_hook" }, + { "pre-format-html", 0, script_hook_pre_format_html, NULL }, + { "get-proxy", 0, script_hook_get_proxy, NULL }, + { "quit", 0, script_hook_quit, NULL }, + NULL_EVENT_HOOK_INFO, +}; diff --git a/contrib/python/hooks.py b/contrib/python/hooks.py index 34a751b..44d619b 100644 --- contrib/python/hooks.py +++ contrib/python/hooks.py @@ -1,60 +1,266 @@ -import re -import sys - -dumbprefixes = { - "7th" : "http://7thguard.net/", - "b" : "http://babelfish.altavista.com/babelfish/tr/", - "bz" : "http://bugzilla.elinks.cz/", - "bug" : "http://bugzilla.elinks.cz/", - "d" : "http://www.dict.org/", - "g" : "http://www.google.com/", - "gg" : "http://www.google.com/", - "go" : "http://www.google.com/", - "fm" : "http://www.freshmeat.net/", - "sf" : "http://www.sourceforge.net/", - "dbug" : "http://bugs.debian.org/", - "dpkg" : "http://packages.debian.org/", - "pycur" : "http://www.python.org/doc/current/", - "pydev" : "http://www.python.org/dev/doc/devel/", - "pyhelp" : "http://starship.python.net/crew/theller/pyhelp.cgi", - "pyvault" : "http://www.vex.net/parnassus/", - "e2" : "http://www.everything2.org/", - "sd" : "http://www.slashdot.org/" -} - -cygwin = re.compile("cygwin\.com") -cygwin_sub1 = re.compile(' None + + Bind a keystroke to a callable object. + + Arguments: + + keystroke -- A string containing a keystroke. The syntax for + keystrokes is described in the elinkskeys(5) man page. + callback -- A callable object to be called when the keystroke is + typed. It will be called without any arguments. + + Optional arguments: + + keymap -- A string containing the name of a keymap. Valid keymap + names can be found in the elinkskeys(5) man page. By + default the "main" keymap is used. + + current_document(...) + current_document() -> string or None + + If a document is being viewed, return its body; otherwise return None. + + current_header(...) + current_header() -> string or None + + If a document is being viewed and it has a header, return the header; + otherwise return None. + + current_link_url(...) + current_link_url() -> string or None + + If a link is selected, return its URL; otherwise return None. + + current_title(...) + current_title() -> string or None + + If a document is being viewed, return its title; otherwise return None. + + current_url(...) + current_url() -> string or None + + If a document is being viewed, return its URL; otherwise return None. + + info_box(...) + info_box(text[, title]) -> None + + Display information to the user in a dialog box. + + Arguments: + + text -- The text to be displayed in the dialog box. This argument can + be a string or any object that has a string representation as + returned by str(object). + + Optional arguments: + + title -- A string containing a title for the dialog box. By default + the string "Info" is used. + + input_box(...) + input_box(prompt, callback, title="User dialog", initial="") -> None + + Display a dialog box to prompt for user input. + + Arguments: + + prompt -- A string containing a prompt for the dialog box. + callback -- A callable object to be called after the dialog is + finished. It will be called with a single argument, which + will be either a string provided by the user or else None + if the user canceled the dialog. + + Optional keyword arguments: + + title -- A string containing a title for the dialog box. By default + the string "User dialog" is used. + initial -- A string containing an initial value for the text entry + field. By default the entry field is initially empty. + + load(...) + load(url, callback) -> None + + Load a document into the ELinks cache and pass its contents to a + callable object. + + Arguments: + + url -- A string containing the URL to load. + callback -- A callable object to be called after the document has + been loaded. It will be called with two arguments: the first + will be a string representing the document's header, or None + if it has no header; the second will be a string representing + the document's body, or None if it has no body. + + menu(...) + menu(items[, type]) -> None + + Display a menu. + + Arguments: + + items -- A sequence of tuples. Each tuple must have two elements: a + string containing the name of a menu item, and a callable + object that will be called without any arguments if the user + selects that menu item. + + Optional arguments: + + type -- A constant specifying the type of menu to display. By default + the menu is displayed at the top of the screen, but if this + argument's value is the constant elinks.MENU_TAB then the menu + is displayed in the same location as the ELinks tab menu. If + its value is the constant elinks.MENU_LINK then the menu is + displayed in the same location as the ELinks link menu and is + not displayed unless a link is currently selected. + + open(...) + open(url, new_tab=False, background=False) -> None + + View a document in either the current tab or a new tab. + + Arguments: + + url -- A string containing the URL to view. + + Optional keyword arguments: + + new_tab -- By default the URL is opened in the current tab. If this + argument's value is the boolean True then the URL is instead + opened in a new tab. + background -- By default a new tab is opened in the foreground. If + this argument's value is the boolean True then a new tab is + instead opened in the background. This argument is ignored + unless new_tab's value is True. + diff --git a/contrib/python/README.Python b/contrib/python/README.Python deleted file mode 100644 index 862b60a..0000000 --- a/contrib/python/README.Python +++ /dev/null @@ -1,8 +0,0 @@ -If you want to use Python scripting with ELinks, add --with-python to the -configure invocation and copy hooks.py to your ~/.elinks directory. - -If configure cannot find Python you can supply a path, e.g. ---with-python=/usr/local/bin if your Python binary is in /usr/local/bin, etc. - -For the present hooks.py is not very usable. You are welcome to make it better. -Good Luck! From kon at iki.fi Tue Sep 19 18:01:17 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Wed, 20 Sep 2006 03:01:17 +0300 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <1117.1158683239@poultrygeist.com> References: <1117.1158683239@poultrygeist.com> Message-ID: <874pv3ieya.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060920/4f05db8d/attachment.bin From fonseca at diku.dk Wed Sep 20 05:17:47 2006 From: fonseca at diku.dk (Jonas Fonseca) Date: Wed, 20 Sep 2006 13:17:47 +0200 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <874pv3ieya.fsf@Astalo.kon.iki.fi> References: <1117.1158683239@poultrygeist.com> <874pv3ieya.fsf@Astalo.kon.iki.fi> Message-ID: <20060920111747.GC26173@diku.dk> Kalle Olavi Niemitalo wrote Wed, Sep 20, 2006: > Perhaps it is a bad idea in general to use the event registration > system for key bindings. There will be only one hook for each > such event, anyway. The event system was introduced for keybindings, when work was being done to support loadable module, especially scripting modules. This has later changed and such modules are no longer supported, so obviously the event system might look like a slightly less fitting way to handle scriptable keybindings. -- Jonas Fonseca From kon at iki.fi Wed Sep 20 15:54:51 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Thu, 21 Sep 2006 00:54:51 +0300 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <1117.1158683239@poultrygeist.com> References: <1117.1158683239@poultrygeist.com> Message-ID: <873bamjj9w.fsf@Astalo.kon.iki.fi> "M. Levinson" writes: > diff --git a/src/scripting/python/python.c b/src/scripting/python/python.c > index cf9e0c0..13411c4 100644 > --- a/src/scripting/python/python.c > +++ b/src/scripting/python/python.c > @@ -4,11 +4,10 @@ #ifdef HAVE_CONFIG_H > #include "config.h" > #endif > > -#include "scripting/python/core.h" > - > #include "elinks.h" > > #include "main/module.h" > +#include "scripting/python/core.h" > #include "scripting/python/hooks.h" > > I get a preprocessor error and reverting the quoted patch fixes it. make -C python all make[3]: Siirryt??n hakemistoon "/home/Kalle/build/i686-pc-linux-gnu/elinks/src/scripting/python" gcc -DHAVE_CONFIG_H -I../../.. -I/home/Kalle/src/elinks/src -I/usr/include/python2.3 -I/usr/include/python2.3 -I/home/Kalle/prefix/include -D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/lib/perl/5.8/CORE -I/usr/include/python2.3 -I/usr/include/python2.3 -I/usr/include -I/usr/include/lua50 -I/usr/include -O0 -ggdb -Wall -Wall -Werror -fno-strict-aliasing -Wno-pointer-sign -o python.o -c /home/Kalle/src/elinks/src/scripting/python/python.c In file included from /usr/include/python2.3/Python.h:8, from /home/Kalle/src/elinks/src/scripting/python/core.h:5, from /home/Kalle/src/elinks/src/scripting/python/python.c:10: /usr/include/python2.3/pyconfig.h:853:1: error: "_POSIX_C_SOURCE" redefined In file included from /usr/include/sys/types.h:27, from /home/Kalle/src/elinks/src/osdep/types.h:9, from /home/Kalle/src/elinks/src/elinks.h:11, from /home/Kalle/src/elinks/src/scripting/python/python.c:7: /usr/include/features.h:150:1: error: this is the location of the previous definition make[3]: *** [python.o] Virhe 1 make[3]: Poistutaan hakemistosta "/home/Kalle/build/i686-pc-linux-gnu/elinks/src/scripting/python" /usr/include/features.h (from from Debian libc6-dev 2.3.6-7) does this: 144 #ifdef _GNU_SOURCE ... 149 # undef _POSIX_C_SOURCE 150 # define _POSIX_C_SOURCE 199506L ... 161 #endif /usr/include/python2.3/pyconfig.h (from Debian python2.3-dev 2.3.5-15) does this: 852 /* Define to activate features from IEEE Stds 1003.1-2001 */ 853 #define _POSIX_C_SOURCE 200112L The -D_GNU_SOURCE option comes from `perl -MExtUtils::Embed -e ccopts` of Debian perl 5.8.8-4. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/07904e22/attachment.bin From kon at iki.fi Thu Sep 21 02:33:23 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Thu, 21 Sep 2006 11:33:23 +0300 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <20060920111747.GC26173@diku.dk> References: <1117.1158683239@poultrygeist.com> <874pv3ieya.fsf@Astalo.kon.iki.fi> <20060920111747.GC26173@diku.dk> Message-ID: <87slilippo.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/002617a5/attachment.bin From fonseca at diku.dk Thu Sep 21 04:57:03 2006 From: fonseca at diku.dk (Jonas Fonseca) Date: Thu, 21 Sep 2006 12:57:03 +0200 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <87slilippo.fsf@Astalo.kon.iki.fi> References: <1117.1158683239@poultrygeist.com> <874pv3ieya.fsf@Astalo.kon.iki.fi> <20060920111747.GC26173@diku.dk> <87slilippo.fsf@Astalo.kon.iki.fi> Message-ID: <20060921105703.GA28458@diku.dk> Kalle Olavi Niemitalo wrote Thu, Sep 21, 2006: > Jonas Fonseca writes: > > > The event system was introduced for keybindings, when work was being > > done to support loadable module, especially scripting modules. > > I see, the event system was first added to CVS in commit > 2b72492bf294c6fc442a06e6eca94fef0154db34 and soon used for > keybindings in commit a4cf28a90691f46b34a2eea581755edcba119bab. > > Anyway, it looks like registering events with generated names can > gradually allocate more and more memory, because unregister_event() > is declared but not called nor implemented. Should I file a bug > report (RESOLVED LATER) on that or add FIXME comments? Yes, this should probably be fixed. Creating a bug might make it more visible, so if you think it is that important this would probably be the best way. > Was unloading a scripting module intended to immediately free all > language-specific objects from memory and change ELinks-side > structures to no longer point to them, or was there going to be a > check so that modules cannot be unloaded when their objects are > in use? I don't know but having the event layer was one way to remove the need for referencing them from ELinks-side structures. -- Jonas Fonseca From levinsm at users.sourceforge.net Thu Sep 21 08:41:55 2006 From: levinsm at users.sourceforge.net (M. Levinson) Date: Thu, 21 Sep 2006 10:41:55 -0400 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <8764hnaq5y.fsf@Astalo.kon.iki.fi> Message-ID: <11469.1158849715@poultrygeist.com> On Sep 21, 2006, Kalle Olavi Niemitalo writes: >I get a preprocessor error and reverting the quoted patch fixes it. Hmm, I was trying to follow this advice from doc/hacking.txt: "Please keep includes in alphabetical order unless a specific order is required for compilation on weird systems (then please at least make a proper comment about that)." Would the attached version of the patch work correctly under Debian? -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: text/x-patch Size: 460 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/a3fee15a/attachment.bin From levinsm at users.sourceforge.net Thu Sep 21 08:21:53 2006 From: levinsm at users.sourceforge.net (M. Levinson) Date: Thu, 21 Sep 2006 10:21:53 -0400 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend Message-ID: <12016.1158848513@poultrygeist.com> On Sep 20, 2006, Kalle Olavi Niemitalo writes: >"M. Levinson" writes: >> The patch includes documentation for Python programmers in doc/python.txt, >> and the documentation is also available internally from Python code via >> Python's introspection API. > >Both currently seem to contain the same text. Would it be a good >idea to generate one from the other, to prevent them from diverging? Absolutely. Attached is an additional Python module to help future maintainers keep them in sync. This still requires manual intervention by a maintainer who's updating the code -- I haven't thought of a simple way to completely automate the process of generating the documentation. (And I wonder if contrib/python is the right place for this file -- end-users will never care about it, but I'm not sure where else to put it. Any thoughts?) >Should CONFIG_SMALL exclude the documentation strings from the >elinks binary? I suppose users of CONFIG_SMALL are unlikely to >link with Python, though. That does seem like a somewhat unlikely combination, but the suggestion makes sense in principle. If there's a consensus in favor of it then I'll rework the patch to make the docstrings conditional. >It would be nice if the comment said this is indirectly used for >the Cancel button as well, and that text is NULL in that case. Okay, I've attached a patch to tweak the comment. >I've been thinking of changing ELinks to allow Unicode characters >in keystroke name strings. It would then be important to know >the encoding of the string. It looks like the cleanest way would >be to use the "es" format unit and explicitly select the "utf_8" >encoding, then tell this to whatever function parses the string >in ELinks. That sounds right to me at the moment, although I may need to study the problem more carefully. >Perhaps it is a bad idea in general to use the event registration >system for key bindings. There will be only one hook for each >such event, anyway. Instead, the event member of struct >keybinding could be replaced with a pointer to a new structure: >[...] > /* Called by free_keybinding(). Free this struct > * scripting_function and any language-specific data to which > * it may point. If the scripting language module uses the > * same struct scripting_function for multiple key bindings, > * then *release might just decrement a reference count. */ > void (*release)(struct scripting_function *); >[...] >You wouldn't then need the keybindings dictionary, I think. Agreed. And getting rid of the keybindings dictionary would certainly simplify the error-handling in python_bind_key(). >Perhaps the same structure could be used for script-generated >menus, too; or perhaps there are better solutions. Yes! And I realize I'm pushing the menu machinery beyond its intended design goals; thoughts on other approaches would be welcome. >The hooks.c and hooks.py patches were inconvenient to apply >because their --- and +++ lines did not have the a/ and b/ >prefixes like the other patches did. Argh, it looks like my git does the wrong thing for a complete rewrite. I apologize for not catching that. Thank you for looking at the code, and for all your feedback. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: text/x-patch Size: 4782 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/8cf7ce8e/attachment.bin -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: text/x-patch Size: 633 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/8cf7ce8e/attachment-0001.bin From kon at iki.fi Thu Sep 21 14:21:01 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Thu, 21 Sep 2006 23:21:01 +0300 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <11469.1158849715@poultrygeist.com> References: <8764hnaq5y.fsf@Astalo.kon.iki.fi> <11469.1158849715@poultrygeist.com> Message-ID: <87eju5gedu.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060921/a6c08467/attachment.bin From levinsm at users.sourceforge.net Fri Sep 22 16:41:07 2006 From: levinsm at users.sourceforge.net (M. Levinson) Date: Fri, 22 Sep 2006 18:41:07 -0400 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend Message-ID: <19177.1158964867@poultrygeist.com> On Sep 22, 2006, Kalle Olavi Niemitalo writes: >> Would the attached version of the patch work correctly under Debian? > >Yes, that works. Great, thanks for testing it. And here's an updated version of the complete patch, which incorporates all of the small follow-up patches I've sent to the list since September 19 along with the following additional tweaks: 0. The bogus --- and +++ lines from git diff are fixed. (sigh) 1. All Python docstrings are omitted if CONFIG_SMALL is defined. 2. In contrib/python/hooks.py I commented out a couple of the default feeds in my feedreader demo just to make it a bit less overwhelming the first time a user runs it, when every entry is considered unseen. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: text/x-patch Size: 79917 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060922/801cef9b/attachment.bin From kon at iki.fi Sat Sep 23 10:35:05 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Sat, 23 Sep 2006 19:35:05 +0300 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend In-Reply-To: <19177.1158964867@poultrygeist.com> References: <19177.1158964867@poultrygeist.com> Message-ID: <87hcyyfsna.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060923/6d905b99/attachment.bin From levinsm at users.sourceforge.net Sun Sep 24 06:04:34 2006 From: levinsm at users.sourceforge.net (M. Levinson) Date: Sun, 24 Sep 2006 08:04:34 -0400 Subject: [elinks-dev] Re: [patch] additional functionality for Python backend Message-ID: <18550.1159099474@poultrygeist.com> On Sep 23, 2006, Kalle Olavi Niemitalo writes: >"M. Levinson" writes: >> +static char python_info_box_doc[] = \ >> +PYTHON_DOCSTRING("info_box(text[, title]) -> None\n\ > >The backslash after the equals sign looks unnecessary, >although it shouldn't hurt either. Of course, I think my brain must have been in Python mode rather than C mode when I typed that.... >> +#define PYTHON_DOCSTRING(str) ("") >> +#endif > >Please remove the parentheses from the macro. >static char python_info_box_doc[] = (""); is not standard C >and is not accepted by Tiny C Compiler 0.9.23. And for this one I have no excuse. Fixed in the attached version - thanks! -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: text/x-patch Size: 80353 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060924/b5823cf2/attachment.bin From kon at iki.fi Wed Sep 27 23:28:16 2006 From: kon at iki.fi (Kalle Olavi Niemitalo) Date: Thu, 28 Sep 2006 08:28:16 +0300 Subject: [elinks-dev] Please add more milestones to bugzilla.elinks.or.cz. Message-ID: <8764f8r24v.fsf@Astalo.kon.iki.fi> A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 188 bytes Desc: not available Url : http://linuxfromscratch.org/pipermail/elinks-dev/attachments/20060928/9070c427/attachment.bin From fonseca at diku.dk Thu Sep 28 08:20:44 2006 From: fonseca at diku.dk (Jonas Fonseca) Date: Thu, 28 Sep 2006 16:20:44 +0200 Subject: [elinks-dev] Please add more milestones to bugzilla.elinks.or.cz. In-Reply-To: <8764f8r24v.fsf@Astalo.kon.iki.fi> References: <8764f8r24v.fsf@Astalo.kon.iki.fi> Message-ID: <20060928142044.GA18560@diku.dk> Kalle Olavi Niemitalo wrote Thu, Sep 28, 2006: > I would like to accept ELinks bug 811 and fix it in the > elinks-0.11 branch and in HEAD. Bugzilla doesn't let me do > that without determining a target milestone, but there are no > milestones between 0.10.0 and 1.0-M1. Please add some milestones > for the 0.11 and 0.12 series, such as 0.11-M1 and 0.12-M1. Ok, done. > Also, would it make sense to hide from the list the milestones > that have already been reached? No one should be targetting any > more bug fixes for e.g. 0.5pre2. I don't know a lot about Bugzilla, but there doesn't seem to be a way to hide them, it's only possible to delete them. -- Jonas Fonseca