diff options
author | Barrucadu <mike@barrucadu.co.uk> | 2009-05-19 12:12:36 +0100 |
---|---|---|
committer | Barrucadu <mike@barrucadu.co.uk> | 2009-05-19 12:12:36 +0100 |
commit | 87912a756633806cc8fb1d1b19a41109fe4fb86a (patch) | |
tree | 67e03d553459fb14049329e901bc29707b78084c | |
parent | 6eb4c0d4a196dc52e950474afc6fc79341b95960 (diff) | |
parent | d228014222c889eb61150a5aa9b2efaf5259b880 (diff) |
Merge commit 'duc/experimental' into experimental
Conflicts:
examples/configs/sampleconfig-dev
-rw-r--r-- | examples/configs/sampleconfig | 21 | ||||
-rw-r--r-- | examples/configs/sampleconfig-dev | 23 | ||||
-rwxr-xr-x | examples/scripts/session.sh | 6 | ||||
-rw-r--r-- | uzbl.c | 238 | ||||
-rw-r--r-- | uzbl.h | 11 |
5 files changed, 211 insertions, 88 deletions
diff --git a/examples/configs/sampleconfig b/examples/configs/sampleconfig index db4abc2..b19b850 100644 --- a/examples/configs/sampleconfig +++ b/examples/configs/sampleconfig @@ -8,16 +8,17 @@ # from insert mode by combining them with the modkey # TODO: ability to attach misc things (spawn <foo>, script <bar>,.. to internal events) -set history_handler = /usr/share/uzbl/examples/scripts/history.sh -set download_handler = /usr/share/uzbl/examples/scripts/download.sh +# You can use any action in place of spawn +set history_handler = spawn /usr/share/uzbl/examples/scripts/history.sh +set download_handler = spawn /usr/share/uzbl/examples/scripts/download.sh + +# TODO: cookie_handler can't take arbitrary actionsyet set cookie_handler = /usr/share/uzbl/examples/scripts/cookies.sh set minimum_font_size = 6 set default_font_size = 11 # use with bind ... = sh <shell-oneliner> -# notice the '' - it's a spacer to keep bash and sh from shifting the positional parameters -# by one, so they will appear in the same position as with scripts invoked via spawn -set shell_cmd = sh -c %s '' +set shell_cmd = sh -c @@ -40,9 +41,9 @@ set always_insert_mode = 0 #set proxy_url = http://127.0.0.1:8118 #values 0-3 #set http_debug = 0 -#set useragent = uzbl (Webkit %webkit-major%.%webkit-minor%.%webkit-micro%) +#set useragent = uzbl (Webkit WEBKIT_MAJOR.WEBKIT_MINOR.WEBKIT_MICRO) # Example user agent containing everything: -#set useragent = Uzbl (Webkit %webkit-major%.%webkit-minor%.%webkit-micro%) (%sysname% %nodename% %kernrel% %kernver% %arch-system% [%arch-uzbl%]) (Commit %commit%) +set useragent = Uzbl (Webkit WEBKIT_MAJOR.WEBKIT_MINOR.WEBKIT_MICRO) (SYSNAME NODENAME KERNREL KERNVER ARCH_SYSTEM [ARCH_UZBL]) (Commit COMMIT) #set max_conns = 0 #set max_conns_host = 0 @@ -85,7 +86,7 @@ bind u = spawn /usr/share/uzbl/examples/scripts/load_url_from_bookmar bind yurl = spawn /usr/share/uzbl/examples/scripts/yank.sh 8 primary bind ytitle = spawn /usr/share/uzbl/examples/scripts/yank.sh 9 clipboard # does the same as yurl but without needing a script -bind y2url = sh 'echo -n $6 | xclip' +bind y2url = sh "echo -n $6 | xclip" # go the page from primary selection bind p = sh "echo act uri `xclip -selection primary -o` > $4" # go to the page in clipboard @@ -95,7 +96,9 @@ bind S = js alert("hi"); # example showing how to use sh # it sends a command to the fifo, whose path is told via a positional param # if fifo_dir is not set, it'll echo to a file named (null) somewhere >:) remember to delete it -bind XS = sh 'echo "act script alert (\"This is sent by the shell via a fifo\")" > "$4"' +# Parameters are separated by spaces and the script body must be one parameter, so enclose it in +# quotes and escape any inner quotes using backslashes +bind XS = sh 'echo "act script alert (\'This is sent by the shell via a fifo\')" > "$4"' # Keyboard based link following: work in progress! No C DOM bindings yet, no click() event for hyperlinks so no referrer set..Quite basic but does the job for now... #hit F to toggle the Hints (now in form of link numbering) bind F= script /usr/share/uzbl/examples/scripts/hint.js diff --git a/examples/configs/sampleconfig-dev b/examples/configs/sampleconfig-dev index fb240dd..a83589f 100644 --- a/examples/configs/sampleconfig-dev +++ b/examples/configs/sampleconfig-dev @@ -8,16 +8,17 @@ # from insert mode by combining them with the modkey # TODO: ability to attach misc things (spawn <foo>, script <bar>,.. to internal events) -set history_handler = ./examples/scripts/history.sh -set download_handler = ./examples/scripts/download.sh +# Usually you want to spawn a script to handle things, but any action (such as sh) can be used +set history_handler = spawn ./examples/scripts/history.sh +set download_handler = spawn ./examples/scripts/download.sh + +# TODO: you can't use actions in cookie handler yet. set cookie_handler = ./examples/scripts/cookies.sh set minimum_font_size = 6 set default_font_size = 11 # use with bind ... = sh <shell-oneliner> -# notice the '' - it's a spacer to keep bash and sh from shifting the positional parameters -# by one, so they will appear in the same position as with scripts invoked via spawn -set shell_cmd = sh -c %s '' +set shell_cmd = sh -c @@ -41,9 +42,9 @@ set always_insert_mode = 0 #set proxy_url = http://127.0.0.1:8118 #values 0-3 #set http_debug = 0 -#set useragent = uzbl (Webkit %webkit-major%.%webkit-minor%.%webkit-micro%) +#set useragent = uzbl (Webkit WEBKIT_MAJOR.WEBKIT_MINOR.WEBKIT_MICRO) # Example user agent containing everything: -#set useragent = Uzbl (Webkit %webkit-major%.%webkit-minor%.%webkit-micro%) (%sysname% %nodename% %kernrel% %kernver% %arch-system% [%arch-uzbl%]) (Commit %commit%) +set useragent = Uzbl (Webkit WEBKIT_MAJOR.WEBKIT_MINOR.WEBKIT_MICRO) (SYSNAME NODENAME KERNREL KERNVER ARCH_SYSTEM [ARCH_UZBL]) (Commit COMMIT) #set max_conns = 0 #set max_conns_host = 0 @@ -79,6 +80,8 @@ bind :wiki _ = uri http://wiki.archlinux.org/index.php/Special:Search?searc bind gg _ = uri http://www.google.com/search?q=%s bind i = toggle_insert_mode bind I = toggle_insert_mode 0 # disable insert mode (1 to enable) +# Enclose the executable in quotes if it has spaces. Any additional parameters you use will +# appear AFTER the default parameters bind B = spawn ./examples/scripts/insert_bookmark.sh bind U = spawn ./examples/scripts/load_url_from_history.sh bind u = spawn ./examples/scripts/load_url_from_bookmarks.sh @@ -96,7 +99,11 @@ bind S = js alert("hi"); # example showing how to use sh # it sends a command to the fifo, whose path is told via a positional param # if fifo_dir is not set, it'll echo to a file named (null) somewhere >:) remember to delete it -bind XS = sh 'echo "act script alert (\"This is sent by the shell via a fifo\")" > "$4"' +# The body of the shell command should be one parameter, so if it has spaces like here, +# you must enclose it in quotes. Remember to escape (and double-escape) quotes and backslashes +# in the body. Any additional parameters you use will appear AFTER the default parameters (cfg file +# path, fifo & socket dirs, etc.) +bind XS = sh 'echo "act script alert (\'This is sent by the shell via a fifo\')" > "$4"' # Keyboard based link following: work in progress! No C DOM bindings yet, no click() event for hyperlinks so no referrer set..Quite basic but does the job for now... #hit F to toggle the Hints (now in form of link numbering) bind F= script ./examples/scripts/hint.js diff --git a/examples/scripts/session.sh b/examples/scripts/session.sh index 5087af0..b8e2bd6 100755 --- a/examples/scripts/session.sh +++ b/examples/scripts/session.sh @@ -13,9 +13,9 @@ sessionfile=$XDG_DATA_HOME/uzbl/session # the file in which the "session" (i.e. UZBL="uzbl" # add custom flags and whatever here. fifodir=/tmp # remember to change this if you instructed uzbl to put its fifos elsewhere -thisfifo="$5" -act="$1" -url="$7" +thisfifo="$4" +act="$8" +url="$6" case $act in "launch" ) @@ -76,6 +76,8 @@ const struct { { "reset_command_mode", (void *)&uzbl.behave.reset_command_mode }, { "modkey" , (void *)&uzbl.behave.modkey }, { "load_finish_handler",(void *)&uzbl.behave.load_finish_handler}, + { "load_start_handler", (void *)&uzbl.behave.load_start_handler }, + { "load_commit_handler",(void *)&uzbl.behave.load_commit_handler}, { "history_handler", (void *)&uzbl.behave.history_handler }, { "download_handler", (void *)&uzbl.behave.download_handler }, { "cookie_handler", (void *)&uzbl.behave.cookie_handler }, @@ -292,7 +294,8 @@ download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) { const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download); if (uzbl.state.verbose) printf("Download -> %s\n",uri); - run_command(uzbl.behave.download_handler, uri, FALSE, NULL); + /* if urls not escaped, we may have to escape and quote uri before this call */ + run_handler(uzbl.behave.download_handler, uri); } return (FALSE); } @@ -304,10 +307,7 @@ scroll (GtkAdjustment* bar, const char *param) { gchar *end; amount = g_ascii_strtod(param, &end); - - if (*end) - fprintf(stderr, "found something after double: %s\n", end); - + if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01; gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount); } @@ -394,9 +394,17 @@ load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { (void) page; (void) frame; (void) data; - if (uzbl.behave.load_finish_handler) { - run_command(uzbl.behave.load_finish_handler, NULL, FALSE, NULL); - } + if (uzbl.behave.load_finish_handler) + run_handler(uzbl.behave.load_finish_handler, ""); +} + +static void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { + (void) page; + (void) frame; + (void) data; + if (uzbl.behave.load_start_handler) + run_handler(uzbl.behave.load_start_handler, ""); } static void @@ -411,6 +419,8 @@ load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) { update_title(); } g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page? + if (uzbl.behave.load_commit_handler) + run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri); } static void @@ -428,11 +438,8 @@ log_history_cb () { char date [80]; time ( &rawtime ); timeinfo = localtime ( &rawtime ); - strftime (date, 80, "%Y-%m-%d %H:%M:%S", timeinfo); - GString* args = g_string_new (""); - g_string_printf (args, "'%s'", date); - run_command(uzbl.behave.history_handler, args->str, FALSE, NULL); - g_string_free (args, TRUE); + strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo); + run_handler(uzbl.behave.history_handler, date); } } @@ -854,72 +861,132 @@ expand_template(const char *template) { } /* --End Statusbar functions-- */ +static void +sharg_append(GArray *a, const gchar *str) { + const gchar *s = (str ? str : ""); + g_array_append_val(a, s); +} // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc) static gboolean -run_command (const char *command, const char *args, const gboolean sync, char **stdout) { +run_command (const gchar *command, const guint npre, const gchar **args, + const gboolean sync, char **stdout) { //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args] - GString *to_execute = g_string_new (""); GError *err = NULL; - gchar *cmd = g_strstrip(g_strdup(command)); - gchar *qcfg = (uzbl.state.config_file ? g_shell_quote(uzbl.state.config_file) : g_strdup("''")); - gchar *qfifo = (uzbl.comm.fifo_path ? g_shell_quote(uzbl.comm.fifo_path) : g_strdup("''")); - gchar *qsock = (uzbl.comm.socket_path ? g_shell_quote(uzbl.comm.socket_path) : g_strdup("''")); - gchar *quri = (uzbl.state.uri ? g_shell_quote(uzbl.state.uri) : g_strdup("''")); - gchar *qtitle = (uzbl.gui.main_title ? g_shell_quote(uzbl.gui.main_title) : g_strdup("''")); - + + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + gchar *pid = itos(getpid()); + gchar *xwin = itos(uzbl.xwin); + guint i; + sharg_append(a, command); + for (i = 0; i < npre; i++) /* add n args before the default vars */ + sharg_append(a, args[i]); + sharg_append(a, uzbl.state.config_file); + sharg_append(a, pid); + sharg_append(a, xwin); + sharg_append(a, uzbl.comm.fifo_path); + sharg_append(a, uzbl.comm.socket_path); + sharg_append(a, uzbl.state.uri); + sharg_append(a, uzbl.gui.main_title); + + for (i = npre; i < g_strv_length((gchar**)args); i++) + sharg_append(a, args[i]); gboolean result; - g_string_printf (to_execute, "%s %s '%i' '%i' %s %s", - cmd, qcfg, (int) getpid(), (int) uzbl.xwin, qfifo, qsock); - g_string_append_printf (to_execute, " %s %s", quri, qtitle); - if(args) g_string_append_printf (to_execute, " %s", args); - - if (sync) { - result = g_spawn_command_line_sync (to_execute->str, stdout, NULL, NULL, &err); - } else result = g_spawn_command_line_async (to_execute->str, &err); - if (uzbl.state.verbose) - printf("Called %s. Result: %s\n", to_execute->str, (result ? "TRUE" : "FALSE" )); - g_string_free (to_execute, TRUE); + if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, stdout, NULL, NULL, &err); + else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, &err); + + if (uzbl.state.verbose) { + GString *s = g_string_new("spawned:"); + for (i = 0; i < (a->len); i++) { + gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i)); + g_string_append_printf(s, " %s", qarg); + g_free (qarg); + } + g_string_append_printf(s, " -- result: %s", (result ? "true" : "false")); + printf("%s\n", s->str); + g_string_free(s, TRUE); + } if (err) { g_printerr("error on run_command: %s\n", err->message); g_error_free (err); } - - g_free (qcfg); - g_free (qfifo); - g_free (qsock); - g_free (quri); - g_free (qtitle); - g_free (cmd); + g_free (pid); + g_free (xwin); + g_array_free (a, TRUE); return result; } +static gchar** +split_quoted(const gchar* src, const gboolean unquote) { + /* split on unquoted space, return array of strings; + remove a layer of quotes and backslashes if unquote */ + gboolean dq = FALSE; + gboolean sq = FALSE; + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + GString *s = g_string_new (""); + const gchar *p; + gchar **ret; + gchar *dup; + for (p = src; *p != '\0'; p++) { + if ((*p == '\\') && unquote) g_string_append_c(s, *++p); + else if (*p == '\\') { g_string_append_c(s, *p++); + g_string_append_c(s, *p); } + else if ((*p == '"') && unquote && !sq) dq = !dq; + else if (*p == '"' && !sq) { g_string_append_c(s, *p); + dq = !dq; } + else if ((*p == '\'') && unquote && !dq) sq = !sq; + else if (*p == '\'' && !dq) { g_string_append_c(s, *p); + sq = ! sq; } + else if ((*p == ' ') && !dq && !sq) { + dup = g_strdup(s->str); + g_array_append_val(a, dup); + g_string_truncate(s, 0); + } else g_string_append_c(s, *p); + } + dup = g_strdup(s->str); + g_array_append_val(a, dup); + ret = (gchar**)a->data; + g_array_free (a, FALSE); + g_string_free (s, FALSE); + return ret; +} + static void spawn(WebKitWebView *web_view, const char *param) { (void)web_view; -/* - TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after - gchar** cmd = g_strsplit(param, " ", 2); - gchar * args = NULL; - if (cmd[1]) { - args = g_shell_quote(cmd[1]); - } - if (cmd) { - run_command(cmd[0], args, FALSE, NULL); - } - if (args) { - g_free(args); - } -*/ - run_command(param, NULL, FALSE, NULL); + //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after + gchar **cmd = split_quoted(param, TRUE); + if (cmd) run_command(cmd[0], 0, &cmd[1], FALSE, NULL); + g_strfreev ((gchar**)cmd); } static void spawn_sh(WebKitWebView *web_view, const char *param) { (void)web_view; - gchar *cmd = g_strdup_printf(uzbl.behave.shell_cmd, param); - spawn(NULL, cmd); - g_free(cmd); + if (!uzbl.behave.shell_cmd) { + g_printerr ("spawn_sh: shell_cmd is not set!\n"); + return; + } + + guint i; + gchar *spacer = g_strdup(""); + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE); + gchar **p = split_quoted(param, TRUE); + for (i = 1; i < g_strv_length(cmd); i++) + sharg_append(a, cmd[i]); + sharg_append(a, p[0]); /* the first param comes right after shell_cmd; + the rest come after default args */ + sharg_append(a, spacer); + for (i = 1; i < g_strv_length(p); i++) + sharg_append(a, p[i]); + if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, a->data, FALSE, NULL); + g_free (spacer); + g_strfreev (cmd); + g_strfreev (p); + g_array_free (a, FALSE); } static void @@ -960,6 +1027,8 @@ get_var_value(gchar *name) { || var_is("title_format_long", name) || var_is("modkey", name) || var_is("load_finish_handler", name) + || var_is("load_start_handler", name) + || var_is("load_commit_handler", name) || var_is("history_handler", name) || var_is("download_handler", name) || var_is("cookie_handler", name) @@ -1033,6 +1102,8 @@ set_var_value(gchar *name, gchar *val) { || var_is("title_format_long", name) || var_is("title_format_short", name) || var_is("load_finish_handler", name) + || var_is("load_start_handler", name) + || var_is("load_commit_handler", name) || var_is("history_handler", name) || var_is("download_handler", name) || var_is("cookie_handler", name)) { @@ -1585,6 +1656,7 @@ create_browser () { g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view); g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view); g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view); + g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view); g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view); g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view); g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view); @@ -1622,6 +1694,28 @@ GtkWidget* create_window () { } static void +run_handler (const gchar *act, const gchar *args) { + char **parts = g_strsplit(act, " ", 2); + if (!parts) return; + else if ((g_strcmp0(parts[0], "spawn") == 0) + || (g_strcmp0(parts[0], "sh") == 0)) { + guint i; + GString *a = g_string_new (""); + char **spawnparts; + spawnparts = split_quoted(parts[1], FALSE); + g_string_append_printf(a, "%s", spawnparts[0]); + if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */ + for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */ + g_string_append_printf(a, " %s", spawnparts[i]); + parse_command(parts[0], a->str); + g_string_free (a, TRUE); + g_strfreev (spawnparts); + } else + parse_command(parts[0], parts[1]); + g_strfreev (parts); +} + +static void add_binding (const gchar *key, const gchar *act) { char **parts = g_strsplit(act, " ", 2); Action *action; @@ -1756,14 +1850,19 @@ static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer use gchar * stdout = NULL; soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL); - GString* args = g_string_new (""); + GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*)); + gchar *action = g_strdup ("GET"); SoupURI * soup_uri = soup_message_get_uri(msg); - g_string_printf (args, "GET %s %s", soup_uri->host, soup_uri->path); - run_command(uzbl.behave.cookie_handler, args->str, TRUE, &stdout); + sharg_append(a, action); + sharg_append(a, soup_uri->host); + sharg_append(a, soup_uri->path); + run_command(uzbl.behave.cookie_handler, 0, a->data, TRUE, &stdout); /* TODO: use handler */ + //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */ if(stdout) { soup_message_headers_replace (msg->request_headers, "Cookie", stdout); } - g_string_free(args, TRUE); + g_free (action); + g_array_free(a, TRUE); } static void @@ -1773,12 +1872,17 @@ save_cookies (SoupMessage *msg, gpointer user_data){ char *cookie; for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){ cookie = soup_cookie_to_set_cookie_header(ck->data); - GString* args = g_string_new (""); + GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*)); SoupURI * soup_uri = soup_message_get_uri(msg); - g_string_printf (args, "PUT %s %s \"%s\"", soup_uri->host, soup_uri->path, cookie); - run_command(uzbl.behave.cookie_handler, args->str, FALSE, NULL); - g_string_free(args, TRUE); - free(cookie); + gchar *action = strdup("PUT"); + sharg_append(a, action); + sharg_append(a, soup_uri->host); + sharg_append(a, soup_uri->path); + sharg_append(a, cookie); + run_command(uzbl.behave.cookie_handler, 0, a->data, FALSE, NULL); + g_free (cookie); + g_free (action); + g_array_free(a, TRUE); } g_slist_free(ck); } @@ -128,6 +128,8 @@ typedef struct { /* behaviour */ typedef struct { gchar* load_finish_handler; + gchar* load_start_handler; + gchar* load_commit_handler; gchar* status_format; gchar* title_format_short; gchar* title_format_long; @@ -243,6 +245,9 @@ static void load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); static void +load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); + +static void load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data); static void @@ -276,7 +281,8 @@ static void close_uzbl (WebKitWebView *page, const char *param); static gboolean -run_command(const char *command, const char *args, const gboolean sync, char **stdout); +run_command(const gchar *command, const guint npre, + const gchar **args, const gboolean sync, char **stdout); static void spawn(WebKitWebView *web_view, const char *param); @@ -339,6 +345,9 @@ static GtkWidget* create_window (); static void +run_handler (const gchar *act, const gchar *args); + +static void add_binding (const gchar *key, const gchar *act); static gchar* |