added insert_indicator and command_indicator
[uzbl-mobile] / uzbl.c
1 /* -*- c-basic-offset: 4; -*- */
2 // Original code taken from the example webkit-gtk+ application. see notice below.
3 // Modified code is licensed under the GPL 3.  See LICENSE file.
4
5
6 /*
7  * Copyright (C) 2006, 2007 Apple Inc.
8  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
27  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32
33 #define LENGTH(x) (sizeof x / sizeof x[0])
34 #define MAX_BINDINGS 256
35
36 #include <gtk/gtk.h>
37 #include <gdk/gdkx.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/un.h>
43 #include <sys/utsname.h>
44 #include <sys/time.h>
45 #include <webkit/webkit.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <stdlib.h>
50 #include <errno.h>
51 #include <string.h>
52 #include <fcntl.h>
53 #include <sys/socket.h>
54 #include <sys/un.h>
55 #include <libsoup/soup.h>
56 #include <signal.h>
57 #include "uzbl.h"
58 #include "config.h"
59
60 static Uzbl uzbl;
61 typedef void (*Command)(WebKitWebView*, GArray *argv);
62
63
64
65 /* commandline arguments (set initial values for the state variables) */
66 static const 
67 GOptionEntry entries[] =
68 {
69     { "uri",     'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri,
70         "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
71     { "verbose", 'v', 0, G_OPTION_ARG_NONE,   &uzbl.state.verbose,
72         "Whether to print all messages or just errors.", NULL },
73     { "name",    'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, 
74         "Name of the current instance (defaults to Xorg window id)", "NAME" },
75     { "config",  'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file,   
76         "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
77     { NULL,      0, 0, 0, NULL, NULL, NULL }
78 };
79
80 /* associate command names to their properties */
81 typedef const struct {
82     void **ptr;
83     int type;
84     int dump;
85     void (*func)(void);
86 } uzbl_cmdprop;
87
88 enum {TYPE_INT, TYPE_STR};
89
90 /* an abbreviation to help keep the table's width humane */
91 #define PTR(var, t, d, fun) { .ptr = (void*)&(var), .type = TYPE_##t, .dump = d, .func = fun }
92
93 const struct {
94     char *name;
95     uzbl_cmdprop cp;
96 } var_name_to_ptr[] = {
97 /*    variable name         pointer to variable in code          type  dump callback function    */
98 /*  --------------------------------------------------------------------------------------- */
99     { "uri",                 PTR(uzbl.state.uri,                  STR,  1,   cmd_load_uri)},
100     { "mode",                PTR(uzbl.behave.mode,                INT,  0,   NULL)},
101     { "inject_html",         PTR(uzbl.behave.inject_html,         STR,  0,   cmd_inject_html)},
102     { "base_url",            PTR(uzbl.behave.base_url,            STR,  1,   NULL)},
103     { "html_endmarker",      PTR(uzbl.behave.html_endmarker,      STR,  1,   NULL)},
104     { "html_mode_timeout",   PTR(uzbl.behave.html_timeout,        INT,  1,   NULL)},
105     { "status_message",      PTR(uzbl.gui.sbar.msg,               STR,  1,   update_title)},
106     { "show_status",         PTR(uzbl.behave.show_status,         INT,  1,   cmd_set_status)},
107     { "status_top",          PTR(uzbl.behave.status_top,          INT,  1,   move_statusbar)},
108     { "status_format",       PTR(uzbl.behave.status_format,       STR,  1,   update_title)},
109     { "status_pbar_done",    PTR(uzbl.gui.sbar.progress_s,        STR,  1,   update_title)},
110     { "status_pbar_pending", PTR(uzbl.gui.sbar.progress_u,        STR,  1,   update_title)},
111     { "status_pbar_width",   PTR(uzbl.gui.sbar.progress_w,        INT,  1,   update_title)},
112     { "status_background",   PTR(uzbl.behave.status_background,   STR,  1,   update_title)},
113     { "insert_indicator",    PTR(uzbl.behave.insert_indicator,    STR,  1,   update_title)},
114     { "command_indicator",   PTR(uzbl.behave.cmd_indicator,       STR,  1,   update_title)},
115     { "title_format_long",   PTR(uzbl.behave.title_format_long,   STR,  1,   update_title)},
116     { "title_format_short",  PTR(uzbl.behave.title_format_short,  STR,  1,   update_title)},
117     { "insert_mode",         PTR(uzbl.behave.insert_mode,         INT,  1,   NULL)},
118     { "always_insert_mode",  PTR(uzbl.behave.always_insert_mode,  INT,  1,   cmd_always_insert_mode)},
119     { "reset_command_mode",  PTR(uzbl.behave.reset_command_mode,  INT,  1,   NULL)},
120     { "modkey",              PTR(uzbl.behave.modkey,              STR,  1,   cmd_modkey)},
121     { "load_finish_handler", PTR(uzbl.behave.load_finish_handler, STR,  1,   NULL)},
122     { "load_start_handler",  PTR(uzbl.behave.load_start_handler,  STR,  1,   NULL)},
123     { "load_commit_handler", PTR(uzbl.behave.load_commit_handler, STR,  1,   NULL)},
124     { "history_handler",     PTR(uzbl.behave.history_handler,     STR,  1,   NULL)},
125     { "download_handler",    PTR(uzbl.behave.download_handler,    STR,  1,   NULL)},
126     { "cookie_handler",      PTR(uzbl.behave.cookie_handler,      STR,  1,   cmd_cookie_handler)},
127     { "fifo_dir",            PTR(uzbl.behave.fifo_dir,            STR,  1,   cmd_fifo_dir)},
128     { "socket_dir",          PTR(uzbl.behave.socket_dir,          STR,  1,   cmd_socket_dir)},
129     { "http_debug",          PTR(uzbl.behave.http_debug,          INT,  1,   cmd_http_debug)},
130     { "shell_cmd",           PTR(uzbl.behave.shell_cmd,           STR,  1,   NULL)},
131     { "proxy_url",           PTR(uzbl.net.proxy_url,              STR,  1,   set_proxy_url)},
132     { "max_conns",           PTR(uzbl.net.max_conns,              INT,  1,   cmd_max_conns)},
133     { "max_conns_host",      PTR(uzbl.net.max_conns_host,         INT,  1,   cmd_max_conns_host)},
134     { "useragent",           PTR(uzbl.net.useragent,              STR,  1,   cmd_useragent)},
135     /* exported WebKitWebSettings properties*/
136     { "font_size",           PTR(uzbl.behave.font_size,           INT,  1,   cmd_font_size)},
137     { "monospace_size",      PTR(uzbl.behave.monospace_size,      INT,  1,   cmd_font_size)},
138     { "minimum_font_size",   PTR(uzbl.behave.minimum_font_size,   INT,  1,   cmd_minimum_font_size)},
139     { "disable_plugins",     PTR(uzbl.behave.disable_plugins,     INT,  1,   cmd_disable_plugins)},
140     { "disable_scripts",     PTR(uzbl.behave.disable_scripts,     INT,  1,   cmd_disable_scripts)},
141     { "autoload_images",     PTR(uzbl.behave.autoload_img,        INT,  1,   cmd_autoload_img)},
142     { "autoshrink_images",   PTR(uzbl.behave.autoshrink_img,      INT,  1,   cmd_autoshrink_img)},
143     { "enable_spellcheck",   PTR(uzbl.behave.enable_spellcheck,   INT,  1,   cmd_enable_spellcheck)},
144     { "enable_private",      PTR(uzbl.behave.enable_private,      INT,  1,   cmd_enable_private)},
145     { "print_backgrounds",   PTR(uzbl.behave.print_bg,            INT,  1,   cmd_print_bg)},
146     { "stylesheet_uri",      PTR(uzbl.behave.style_uri,           STR,  1,   cmd_style_uri)},
147     { "resizable_text_areas",PTR(uzbl.behave.resizable_txt,       INT,  1,   cmd_resizable_txt)},
148     { "default_encoding",    PTR(uzbl.behave.default_encoding,    STR,  1,   cmd_default_encoding)},
149     { "enforce_96_dpi",      PTR(uzbl.behave.enforce_96dpi,       INT,  1,   cmd_enforce_96dpi)},
150     { "caret_browsing",      PTR(uzbl.behave.caret_browsing,      INT,  1,   cmd_caret_browsing)},
151
152     { NULL,                  {.ptr = NULL, .type = TYPE_INT, .dump = 0, .func = NULL}}
153 }, *n2v_p = var_name_to_ptr;
154
155 const struct {
156     char *key;
157     guint mask;
158 } modkeys[] = {
159     { "SHIFT",   GDK_SHIFT_MASK   }, // shift
160     { "LOCK",    GDK_LOCK_MASK    }, // capslock or shiftlock, depending on xserver's modmappings
161     { "CONTROL", GDK_CONTROL_MASK }, // control
162     { "MOD1",    GDK_MOD1_MASK    }, // 4th mod - normally alt but depends on modmappings
163     { "MOD2",    GDK_MOD2_MASK    }, // 5th mod
164     { "MOD3",    GDK_MOD3_MASK    }, // 6th mod
165     { "MOD4",    GDK_MOD4_MASK    }, // 7th mod
166     { "MOD5",    GDK_MOD5_MASK    }, // 8th mod
167     { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
168     { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
169     { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
170     { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
171     { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
172     { "SUPER",   GDK_SUPER_MASK   }, // super (since 2.10)
173     { "HYPER",   GDK_HYPER_MASK   }, // hyper (since 2.10)
174     { "META",    GDK_META_MASK    }, // meta (since 2.10)
175     { NULL,      0                }
176 };
177
178
179 /* construct a hash from the var_name_to_ptr array for quick access */
180 static void
181 make_var_to_name_hash() {
182     uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
183     while(n2v_p->name) {
184         g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
185         n2v_p++;
186     }
187 }
188
189
190 /* --- UTILITY FUNCTIONS --- */
191
192 char *
193 itos(int val) {
194     char tmp[20];
195
196     snprintf(tmp, sizeof(tmp), "%i", val);
197     return g_strdup(tmp);
198 }
199
200 static gchar*
201 strfree(gchar *str) { g_free(str); return NULL; }  // for freeing & setting to null in one go
202
203 static gchar*
204 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
205
206 static char *
207 str_replace (const char* search, const char* replace, const char* string) {
208     gchar **buf;
209     char *ret;
210
211     buf = g_strsplit (string, search, -1);
212     ret = g_strjoinv (replace, buf);
213     g_strfreev(buf); // somebody said this segfaults
214
215     return ret;
216 }
217
218 static GArray*
219 read_file_by_line (gchar *path) {
220     GIOChannel *chan = NULL;
221     gchar *readbuf = NULL;
222     gsize len;
223     GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
224     int i = 0;
225     
226     chan = g_io_channel_new_file(path, "r", NULL);
227     
228     if (chan) {
229         while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
230             const gchar* val = g_strdup (readbuf);
231             g_array_append_val (lines, val);
232             g_free (readbuf);
233             i ++;
234         }
235         
236         g_io_channel_unref (chan);
237     } else {
238         fprintf(stderr, "File '%s' not be read.\n", path);
239     }
240     
241     return lines;
242 }
243
244 static
245 gchar* parseenv (char* string) {
246     extern char** environ;
247     gchar* tmpstr = NULL;
248     int i = 0;
249     
250
251     while (environ[i] != NULL) {
252         gchar** env = g_strsplit (environ[i], "=", 2);
253         gchar* envname = g_strconcat ("$", env[0], NULL);
254
255         if (g_strrstr (string, envname) != NULL) {
256             tmpstr = g_strdup(string);
257             g_free (string);
258             string = str_replace(envname, env[1], tmpstr);
259             g_free (tmpstr);
260         }
261
262         g_free (envname);
263         g_strfreev (env); // somebody said this breaks uzbl
264         i++;
265     }
266
267     return string;
268 }
269
270 static sigfunc*
271 setup_signal(int signr, sigfunc *shandler) {
272     struct sigaction nh, oh;
273
274     nh.sa_handler = shandler;
275     sigemptyset(&nh.sa_mask);
276     nh.sa_flags = 0;
277
278     if(sigaction(signr, &nh, &oh) < 0)
279         return SIG_ERR;
280
281     return NULL;
282 }
283
284 static void
285 clean_up(void) {
286     if (uzbl.behave.fifo_dir)
287         unlink (uzbl.comm.fifo_path);
288     if (uzbl.behave.socket_dir)
289         unlink (uzbl.comm.socket_path);
290
291     g_free(uzbl.state.executable_path);
292     g_string_free(uzbl.state.keycmd, TRUE);
293     g_hash_table_destroy(uzbl.bindings);
294     g_hash_table_destroy(uzbl.behave.commands);
295 }
296
297 /* used for html_mode_timeout 
298  * be sure to extend this function to use
299  * more timers if needed in other places
300 */
301 static void
302 set_timeout(int seconds) {
303     struct itimerval t;
304     memset(&t, 0, sizeof t);
305
306     t.it_value.tv_sec =  seconds;
307     t.it_value.tv_usec = 0;
308     setitimer(ITIMER_REAL, &t, NULL);
309 }
310
311 /* --- SIGNAL HANDLER --- */
312
313 static void
314 catch_sigterm(int s) {
315     (void) s;
316     clean_up();
317 }
318
319 static void
320 catch_sigint(int s) {
321     (void) s;
322     clean_up();
323     exit(EXIT_SUCCESS);
324 }
325
326 static void
327 catch_alrm(int s) {
328     (void) s;
329
330     set_var_value("mode", "0");
331     render_html();
332 }
333
334
335 /* --- CALLBACKS --- */
336
337 static gboolean
338 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
339     (void) web_view;
340     (void) frame;
341     (void) navigation_action;
342     (void) policy_decision;
343     (void) user_data;
344     const gchar* uri = webkit_network_request_get_uri (request);
345     if (uzbl.state.verbose)
346         printf("New window requested -> %s \n", uri);
347     new_window_load_uri(uri);
348     return (FALSE);
349 }
350
351 WebKitWebView*
352 create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer user_data) {
353     (void) web_view;
354     (void) frame;
355     (void) user_data;
356     if (uzbl.state.selected_url != NULL) {
357         if (uzbl.state.verbose)
358             printf("\nNew web view -> %s\n",uzbl.state.selected_url);
359         new_window_load_uri(uzbl.state.selected_url);
360     } else {
361         if (uzbl.state.verbose)
362             printf("New web view -> %s\n","Nothing to open, exiting");
363     }
364     return (NULL);
365 }
366
367 static gboolean
368 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
369     (void) web_view;
370     (void) user_data;
371     if (uzbl.behave.download_handler) {
372         const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
373         if (uzbl.state.verbose)
374             printf("Download -> %s\n",uri);
375         /* if urls not escaped, we may have to escape and quote uri before this call */
376         run_handler(uzbl.behave.download_handler, uri);
377     }
378     return (FALSE);
379 }
380
381 /* scroll a bar in a given direction */
382 static void
383 scroll (GtkAdjustment* bar, GArray *argv) {
384     gdouble amount;
385     gchar *end;
386
387     amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
388     if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
389     gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
390 }
391
392 static void
393 scroll_begin(WebKitWebView* page, GArray *argv) {
394     (void) page; (void) argv;
395     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
396 }
397
398 static void
399 scroll_end(WebKitWebView* page, GArray *argv) {
400     (void) page; (void) argv;
401     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
402                               gtk_adjustment_get_page_size(uzbl.gui.bar_v));
403 }
404
405 static void
406 scroll_vert(WebKitWebView* page, GArray *argv) {
407     (void) page;
408     scroll(uzbl.gui.bar_v, argv);
409 }
410
411 static void
412 scroll_horz(WebKitWebView* page, GArray *argv) {
413     (void) page;
414     scroll(uzbl.gui.bar_h, argv);
415 }
416
417 static void
418 cmd_set_status() {
419     if (!uzbl.behave.show_status) {
420         gtk_widget_hide(uzbl.gui.mainbar);
421     } else {
422         gtk_widget_show(uzbl.gui.mainbar);
423     }
424     update_title();
425 }
426
427 static void
428 toggle_status_cb (WebKitWebView* page, GArray *argv) {
429     (void)page;
430     (void)argv;
431
432     if (uzbl.behave.show_status) {
433         gtk_widget_hide(uzbl.gui.mainbar);
434     } else {
435         gtk_widget_show(uzbl.gui.mainbar);
436     }
437     uzbl.behave.show_status = !uzbl.behave.show_status;
438     update_title();
439 }
440
441 static void
442 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
443     (void) page;
444     (void) title;
445     (void) data;
446     //Set selected_url state variable
447     g_free(uzbl.state.selected_url);
448     uzbl.state.selected_url = NULL;
449     if (link) {
450         uzbl.state.selected_url = g_strdup(link);
451     }
452     update_title();
453 }
454
455 static void
456 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
457     (void) web_view;
458     (void) web_frame;
459     (void) data;
460     if (uzbl.gui.main_title)
461         g_free (uzbl.gui.main_title);
462     uzbl.gui.main_title = g_strdup (title);
463     update_title();
464 }
465
466 static void
467 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
468     (void) page;
469     (void) data;
470     uzbl.gui.sbar.load_progress = progress;
471     update_title();
472 }
473
474 static void
475 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
476     (void) page;
477     (void) frame;
478     (void) data;
479     if (uzbl.behave.load_finish_handler)
480         run_handler(uzbl.behave.load_finish_handler, "");
481 }
482
483 static void
484 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
485     (void) page;
486     (void) frame;
487     (void) data;
488     g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
489     if (uzbl.behave.load_start_handler)
490         run_handler(uzbl.behave.load_start_handler, "");
491 }
492
493 static void
494 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
495     (void) page;
496     (void) data;
497     g_free (uzbl.state.uri);
498     GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
499     uzbl.state.uri = g_string_free (newuri, FALSE);
500     if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
501         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
502         update_title();
503     }
504     if (uzbl.behave.load_commit_handler)
505         run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
506 }
507
508 static void
509 destroy_cb (GtkWidget* widget, gpointer data) {
510     (void) widget;
511     (void) data;
512     gtk_main_quit ();
513 }
514
515 static void
516 log_history_cb () {
517    if (uzbl.behave.history_handler) {
518        time_t rawtime;
519        struct tm * timeinfo;
520        char date [80];
521        time ( &rawtime );
522        timeinfo = localtime ( &rawtime );
523        strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
524        run_handler(uzbl.behave.history_handler, date);
525    }
526 }
527
528
529 /* VIEW funcs (little webkit wrappers) */
530 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
531 VIEWFUNC(reload)
532 VIEWFUNC(reload_bypass_cache)
533 VIEWFUNC(stop_loading)
534 VIEWFUNC(zoom_in)
535 VIEWFUNC(zoom_out)
536 VIEWFUNC(go_back)
537 VIEWFUNC(go_forward)
538 #undef VIEWFUNC
539
540 /* -- command to callback/function map for things we cannot attach to any signals */
541 // TODO: reload
542 static struct {char *name; Command command[2];} cmdlist[] =
543 {   /* key                   function      no_split      */
544     { "back",               {view_go_back, 0}              },
545     { "forward",            {view_go_forward, 0}           },
546     { "scroll_vert",        {scroll_vert, 0}               },
547     { "scroll_horz",        {scroll_horz, 0}               },
548     { "scroll_begin",       {scroll_begin, 0}              },
549     { "scroll_end",         {scroll_end, 0}                },
550     { "reload",             {view_reload, 0},              },
551     { "reload_ign_cache",   {view_reload_bypass_cache, 0}  },
552     { "stop",               {view_stop_loading, 0},        },
553     { "zoom_in",            {view_zoom_in, 0},             }, //Can crash (when max zoom reached?).
554     { "zoom_out",           {view_zoom_out, 0},            },
555     { "uri",                {load_uri, NOSPLIT}            },
556     { "js",                 {run_js, NOSPLIT}              },
557     { "script",             {run_external_js, 0}           },
558     { "toggle_status",      {toggle_status_cb, 0}          },
559     { "spawn",              {spawn, 0}                     },
560     { "sync_spawn",         {spawn_sync, 0}                }, // needed for cookie handler
561     { "sh",                 {spawn_sh, 0}                  },
562     { "sync_sh",            {spawn_sh_sync, 0}             }, // needed for cookie handler
563     { "exit",               {close_uzbl, 0}                },
564     { "search",             {search_forward_text, NOSPLIT} },
565     { "search_reverse",     {search_reverse_text, NOSPLIT} },
566     { "dehilight",          {dehilight, 0}                 },
567     { "toggle_insert_mode", {toggle_insert_mode, 0}        },
568     { "runcmd",             {runcmd, NOSPLIT}              },
569     { "set",                {set_var, NOSPLIT}             },
570     { "dump_config",        {act_dump_config, 0}           }
571 };
572
573 static void
574 commands_hash(void)
575 {
576     unsigned int i;
577     uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
578
579     for (i = 0; i < LENGTH(cmdlist); i++)
580         g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
581 }
582
583 /* -- CORE FUNCTIONS -- */
584
585 void
586 free_action(gpointer act) {
587     Action *action = (Action*)act;
588     g_free(action->name);
589     if (action->param)
590         g_free(action->param);
591     g_free(action);
592 }
593
594 Action*
595 new_action(const gchar *name, const gchar *param) {
596     Action *action = g_new(Action, 1);
597
598     action->name = g_strdup(name);
599     if (param)
600         action->param = g_strdup(param);
601     else
602         action->param = NULL;
603
604     return action;
605 }
606
607 static bool
608 file_exists (const char * filename) {
609     return (access(filename, F_OK) == 0);
610 }
611
612 static void
613 set_var(WebKitWebView *page, GArray *argv) {
614     (void) page;
615     gchar *ctl_line;
616
617     ctl_line = g_strdup_printf("%s %s", "set", argv_idx(argv, 0));
618     parse_cmd_line(ctl_line);
619     g_free(ctl_line);
620 }
621
622 static void
623 act_dump_config() {
624     dump_config();
625 }
626
627 static void
628 toggle_insert_mode(WebKitWebView *page, GArray *argv) {
629     (void)page;
630
631     if (argv_idx(argv, 0)) {
632         if (strcmp (argv_idx(argv, 0), "0") == 0) {
633             uzbl.behave.insert_mode = FALSE;
634         } else {
635             uzbl.behave.insert_mode = TRUE;
636         }
637     } else {
638         uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
639     }
640
641     update_title();
642 }
643
644 static void
645 load_uri (WebKitWebView *web_view, GArray *argv) {
646     if (argv_idx(argv, 0)) {
647         GString* newuri = g_string_new (argv_idx(argv, 0));
648         if (g_strrstr (argv_idx(argv, 0), "://") == NULL)
649             g_string_prepend (newuri, "http://");
650         /* if we do handle cookies, ask our handler for them */
651         webkit_web_view_load_uri (web_view, newuri->str);
652         g_string_free (newuri, TRUE);
653     }
654 }
655
656 static void
657 run_js (WebKitWebView * web_view, GArray *argv) {
658     if (argv_idx(argv, 0))
659         webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
660 }
661
662 static void
663 run_external_js (WebKitWebView * web_view, GArray *argv) {
664     if (argv_idx(argv, 0)) {
665         GArray* lines = read_file_by_line (argv_idx (argv, 0));
666         gchar*  js = NULL;
667         int i = 0;
668         gchar* line;
669
670         while ((line = g_array_index(lines, gchar*, i))) {
671             if (js == NULL) {
672                 js = g_strdup (line);
673             } else {
674                 gchar* newjs = g_strconcat (js, line, NULL);
675                 js = newjs;
676             }
677             i ++;
678             g_free (line);
679         }
680         
681         if (uzbl.state.verbose)
682             printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0));
683
684         if (argv_idx (argv, 1)) {
685             gchar* newjs = str_replace("%s", argv_idx (argv, 1), js);
686             g_free (js);
687             js = newjs;
688         }
689         webkit_web_view_execute_script (web_view, js);
690         g_free (js);
691         g_array_free (lines, TRUE);
692     }
693 }
694
695 static void
696 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
697     if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0')) {
698         if (g_strcmp0 (uzbl.state.searchtx, argv_idx(argv, 0)) != 0) {
699             webkit_web_view_unmark_text_matches (page);
700             webkit_web_view_mark_text_matches (page, argv_idx(argv, 0), FALSE, 0);
701             uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
702         }
703     }
704     
705     if (uzbl.state.searchtx) {
706         if (uzbl.state.verbose)
707             printf ("Searching: %s\n", uzbl.state.searchtx);
708         webkit_web_view_set_highlight_text_matches (page, TRUE);
709         webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
710     }
711 }
712
713 static void
714 search_forward_text (WebKitWebView *page, GArray *argv) {
715     search_text(page, argv, TRUE);
716 }
717
718 static void
719 search_reverse_text (WebKitWebView *page, GArray *argv) {
720     search_text(page, argv, FALSE);
721 }
722
723 static void
724 dehilight (WebKitWebView *page, GArray *argv) {
725     (void) argv;
726     webkit_web_view_set_highlight_text_matches (page, FALSE);
727 }
728
729
730 static void
731 new_window_load_uri (const gchar * uri) {
732     GString* to_execute = g_string_new ("");
733     g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
734     int i;
735     for (i = 0; entries[i].long_name != NULL; i++) {
736         if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
737             gchar** str = (gchar**)entries[i].arg_data;
738             if (*str!=NULL) {
739                 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
740             }
741         }
742     }
743     if (uzbl.state.verbose)
744         printf("\n%s\n", to_execute->str);
745     g_spawn_command_line_async (to_execute->str, NULL);
746     g_string_free (to_execute, TRUE);
747 }
748
749 static void
750 close_uzbl (WebKitWebView *page, GArray *argv) {
751     (void)page;
752     (void)argv;
753     gtk_main_quit ();
754 }
755
756 /* --Statusbar functions-- */
757 static char*
758 build_progressbar_ascii(int percent) {
759    int width=uzbl.gui.sbar.progress_w;
760    int i;
761    double l;
762    GString *bar = g_string_new("");
763
764    l = (double)percent*((double)width/100.);
765    l = (int)(l+.5)>=(int)l ? l+.5 : l;
766
767    for(i=0; i<(int)l; i++)
768        g_string_append(bar, uzbl.gui.sbar.progress_s);
769
770    for(; i<width; i++)
771        g_string_append(bar, uzbl.gui.sbar.progress_u);
772
773    return g_string_free(bar, FALSE);
774 }
775
776 static void
777 setup_scanner() {
778      const GScannerConfig scan_config = {
779              (
780               "\t\r\n"
781              )            /* cset_skip_characters */,
782              (
783               G_CSET_a_2_z
784               "_#"
785               G_CSET_A_2_Z
786              )            /* cset_identifier_first */,
787              (
788               G_CSET_a_2_z
789               "_0123456789"
790               G_CSET_A_2_Z
791               G_CSET_LATINS
792               G_CSET_LATINC
793              )            /* cset_identifier_nth */,
794              ( "" )    /* cpair_comment_single */,
795
796              TRUE         /* case_sensitive */,
797
798              FALSE        /* skip_comment_multi */,
799              FALSE        /* skip_comment_single */,
800              FALSE        /* scan_comment_multi */,
801              TRUE         /* scan_identifier */,
802              TRUE         /* scan_identifier_1char */,
803              FALSE        /* scan_identifier_NULL */,
804              TRUE         /* scan_symbols */,
805              FALSE        /* scan_binary */,
806              FALSE        /* scan_octal */,
807              FALSE        /* scan_float */,
808              FALSE        /* scan_hex */,
809              FALSE        /* scan_hex_dollar */,
810              FALSE        /* scan_string_sq */,
811              FALSE        /* scan_string_dq */,
812              TRUE         /* numbers_2_int */,
813              FALSE        /* int_2_float */,
814              FALSE        /* identifier_2_string */,
815              FALSE        /* char_2_token */,
816              FALSE        /* symbol_2_token */,
817              TRUE         /* scope_0_fallback */,
818              FALSE,
819              TRUE
820      };
821
822      uzbl.scan = g_scanner_new(&scan_config);
823      while(symp->symbol_name) {
824          g_scanner_scope_add_symbol(uzbl.scan, 0,
825                          symp->symbol_name,
826                          GINT_TO_POINTER(symp->symbol_token));
827          symp++;
828      }
829 }
830
831 static gchar *
832 expand_template(const char *template, gboolean escape_markup) {
833      if(!template) return NULL;
834
835      GTokenType token = G_TOKEN_NONE;
836      GString *ret = g_string_new("");
837      char *buf=NULL;
838      int sym;
839
840      g_scanner_input_text(uzbl.scan, template, strlen(template));
841      while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
842          token = g_scanner_get_next_token(uzbl.scan);
843
844          if(token == G_TOKEN_SYMBOL) {
845              sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
846              switch(sym) {
847                  case SYM_URI:
848                      if(escape_markup) {
849                          buf = uzbl.state.uri?
850                              g_markup_printf_escaped("%s", uzbl.state.uri):g_strdup("");
851                          g_string_append(ret, buf);
852                          g_free(buf);
853                      }
854                      else
855                          g_string_append(ret, uzbl.state.uri?
856                                  uzbl.state.uri:g_strdup(""));
857                      break;
858                  case SYM_LOADPRGS:
859                      buf = itos(uzbl.gui.sbar.load_progress);
860                      g_string_append(ret, buf);
861                      g_free(buf);
862                      break;
863                  case SYM_LOADPRGSBAR:
864                      buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
865                      g_string_append(ret, buf);
866                      g_free(buf);
867                      break;
868                  case SYM_TITLE:
869                      if(escape_markup) {
870                          buf = uzbl.gui.main_title?
871                              g_markup_printf_escaped("%s", uzbl.gui.main_title):g_strdup("");
872                          g_string_append(ret, buf);
873                          g_free(buf);
874                      }
875                      else
876                          g_string_append(ret, uzbl.gui.main_title?
877                                  uzbl.gui.main_title:g_strdup(""));
878                      break;
879                  case SYM_SELECTED_URI:
880                      if(escape_markup) {
881                          buf = uzbl.state.selected_url?
882                              g_markup_printf_escaped("%s", uzbl.state.selected_url):g_strdup("");
883                          g_string_append(ret, buf);
884                          g_free(buf);
885                      }
886                      else
887                          g_string_append(ret, uzbl.state.selected_url?
888                                  uzbl.state.selected_url:g_strdup(""));
889                      break;
890                  case SYM_NAME:
891                      buf = itos(uzbl.xwin);
892                      g_string_append(ret,
893                              uzbl.state.instance_name?uzbl.state.instance_name:buf);
894                      g_free(buf);
895                      break;
896                  case SYM_KEYCMD:
897                      if(escape_markup) {
898                          buf = uzbl.state.keycmd->str?
899                              g_markup_printf_escaped("%s", uzbl.state.keycmd->str):g_strdup("");
900                          g_string_append(ret, buf);
901                          g_free(buf);
902                      }
903                      else
904                          g_string_append(ret, uzbl.state.keycmd->str?
905                                  uzbl.state.keycmd->str:g_strdup(""));
906                      break;
907                  case SYM_MODE:
908                      g_string_append(ret,
909                              uzbl.behave.insert_mode?
910                              uzbl.behave.insert_indicator:uzbl.behave.cmd_indicator);
911                      break;
912                  case SYM_MSG:
913                      g_string_append(ret,
914                              uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
915                      break;
916                      /* useragent syms */
917                  case SYM_WK_MAJ:
918                      buf = itos(WEBKIT_MAJOR_VERSION);
919                      g_string_append(ret, buf);
920                      g_free(buf);
921                      break;
922                  case SYM_WK_MIN:
923                      buf = itos(WEBKIT_MINOR_VERSION);
924                      g_string_append(ret, buf);
925                      g_free(buf);
926                      break;
927                  case SYM_WK_MIC:
928                      buf = itos(WEBKIT_MICRO_VERSION);
929                      g_string_append(ret, buf);
930                      g_free(buf);
931                      break;
932                  case SYM_SYSNAME:
933                      g_string_append(ret, uzbl.state.unameinfo.sysname);
934                      break;
935                  case SYM_NODENAME:
936                      g_string_append(ret, uzbl.state.unameinfo.nodename);
937                      break;
938                  case SYM_KERNREL:
939                      g_string_append(ret, uzbl.state.unameinfo.release);
940                      break;
941                  case SYM_KERNVER:
942                      g_string_append(ret, uzbl.state.unameinfo.version);
943                      break;
944                  case SYM_ARCHSYS:
945                      g_string_append(ret, uzbl.state.unameinfo.machine);
946                      break;
947                  case SYM_ARCHUZBL:
948                      g_string_append(ret, ARCH);
949                      break;
950 #ifdef _GNU_SOURCE
951                  case SYM_DOMAINNAME:
952                      g_string_append(ret, uzbl.state.unameinfo.domainname);
953                      break;
954 #endif
955                  case SYM_COMMIT:
956                      g_string_append(ret, COMMIT);
957                      break;
958                  default:
959                      break;
960              }
961          }
962          else if(token == G_TOKEN_INT) {
963              buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
964              g_string_append(ret, buf);
965              g_free(buf);
966          }
967          else if(token == G_TOKEN_IDENTIFIER) {
968              g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
969          }
970          else if(token == G_TOKEN_CHAR) {
971              g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
972          }
973      }
974
975      return g_string_free(ret, FALSE);
976 }
977 /* --End Statusbar functions-- */
978
979 static void
980 sharg_append(GArray *a, const gchar *str) {
981     const gchar *s = (str ? str : "");
982     g_array_append_val(a, s);
983 }
984
985 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
986 static gboolean
987 run_command (const gchar *command, const guint npre, const gchar **args,
988              const gboolean sync, char **stdout) {
989    //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
990     GError *err = NULL;
991     
992     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
993     gchar *pid = itos(getpid());
994     gchar *xwin = itos(uzbl.xwin);
995     guint i;
996     sharg_append(a, command);
997     for (i = 0; i < npre; i++) /* add n args before the default vars */
998         sharg_append(a, args[i]);
999     sharg_append(a, uzbl.state.config_file);
1000     sharg_append(a, pid);
1001     sharg_append(a, xwin);
1002     sharg_append(a, uzbl.comm.fifo_path);
1003     sharg_append(a, uzbl.comm.socket_path);
1004     sharg_append(a, uzbl.state.uri);
1005     sharg_append(a, uzbl.gui.main_title);
1006
1007     for (i = npre; i < g_strv_length((gchar**)args); i++)
1008         sharg_append(a, args[i]);
1009     
1010     gboolean result;
1011     if (sync) {
1012         if (*stdout) *stdout = strfree(*stdout);
1013         
1014         result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
1015                               NULL, NULL, stdout, NULL, NULL, &err);
1016     } else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
1017                                   NULL, NULL, NULL, &err);
1018
1019     if (uzbl.state.verbose) {
1020         GString *s = g_string_new("spawned:");
1021         for (i = 0; i < (a->len); i++) {
1022             gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
1023             g_string_append_printf(s, " %s", qarg);
1024             g_free (qarg);
1025         }
1026         g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
1027         printf("%s\n", s->str);
1028         g_string_free(s, TRUE);
1029     }
1030     if (err) {
1031         g_printerr("error on run_command: %s\n", err->message);
1032         g_error_free (err);
1033     }
1034     g_free (pid);
1035     g_free (xwin);
1036     g_array_free (a, TRUE);
1037     return result;
1038 }
1039
1040 static gchar**
1041 split_quoted(const gchar* src, const gboolean unquote) {
1042     /* split on unquoted space, return array of strings;
1043        remove a layer of quotes and backslashes if unquote */
1044     if (!src) return NULL;
1045     
1046     gboolean dq = FALSE;
1047     gboolean sq = FALSE;
1048     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1049     GString *s = g_string_new ("");
1050     const gchar *p;
1051     gchar **ret;
1052     gchar *dup;
1053     for (p = src; *p != '\0'; p++) {
1054         if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
1055         else if (*p == '\\') { g_string_append_c(s, *p++);
1056                                g_string_append_c(s, *p); }
1057         else if ((*p == '"') && unquote && !sq) dq = !dq;
1058         else if (*p == '"' && !sq) { g_string_append_c(s, *p);
1059                                      dq = !dq; }
1060         else if ((*p == '\'') && unquote && !dq) sq = !sq;
1061         else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
1062                                       sq = ! sq; }
1063         else if ((*p == ' ') && !dq && !sq) {
1064             dup = g_strdup(s->str);
1065             g_array_append_val(a, dup);
1066             g_string_truncate(s, 0);
1067         } else g_string_append_c(s, *p);
1068     }
1069     dup = g_strdup(s->str);
1070     g_array_append_val(a, dup);
1071     ret = (gchar**)a->data;
1072     g_array_free (a, FALSE);
1073     g_string_free (s, TRUE);
1074     return ret;
1075 }
1076
1077 static void
1078 spawn(WebKitWebView *web_view, GArray *argv) {
1079     (void)web_view;
1080     //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
1081     if (argv_idx(argv, 0))
1082         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))), FALSE, NULL);
1083 }
1084
1085 static void
1086 spawn_sync(WebKitWebView *web_view, GArray *argv) {
1087     (void)web_view;
1088     
1089     if (argv_idx(argv, 0))
1090         run_command(argv_idx(argv, 0), 0, ((const gchar **) (argv->data + sizeof(gchar*))),
1091                     TRUE, &uzbl.comm.sync_stdout);
1092 }
1093
1094 static void
1095 spawn_sh(WebKitWebView *web_view, GArray *argv) {
1096     (void)web_view;
1097     if (!uzbl.behave.shell_cmd) {
1098         g_printerr ("spawn_sh: shell_cmd is not set!\n");
1099         return;
1100     }
1101     
1102     guint i;
1103     gchar *spacer = g_strdup("");
1104     g_array_insert_val(argv, 1, spacer);
1105     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1106
1107     for (i = 1; i < g_strv_length(cmd); i++)
1108         g_array_prepend_val(argv, cmd[i]);
1109
1110     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL);
1111     g_free (spacer);
1112     g_strfreev (cmd);
1113 }
1114
1115 static void
1116 spawn_sh_sync(WebKitWebView *web_view, GArray *argv) {
1117     (void)web_view;
1118     if (!uzbl.behave.shell_cmd) {
1119         g_printerr ("spawn_sh_sync: shell_cmd is not set!\n");
1120         return;
1121     }
1122     
1123     guint i;
1124     gchar *spacer = g_strdup("");
1125     g_array_insert_val(argv, 1, spacer);
1126     gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
1127
1128     for (i = 1; i < g_strv_length(cmd); i++)
1129         g_array_prepend_val(argv, cmd[i]);
1130          
1131     if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data,
1132                          TRUE, &uzbl.comm.sync_stdout);
1133     g_free (spacer);
1134     g_strfreev (cmd);
1135 }
1136
1137 static void
1138 parse_command(const char *cmd, const char *param) {
1139     Command *c;
1140
1141     if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
1142
1143             guint i;
1144             gchar **par = split_quoted(param, TRUE);
1145             GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1146
1147             if (c[1] == NOSPLIT) { /* don't split */
1148                 sharg_append(a, param);
1149             } else if (par) {
1150                 for (i = 0; i < g_strv_length(par); i++)
1151                     sharg_append(a, par[i]);
1152             }
1153             c[0](uzbl.gui.web_view, a);
1154             g_strfreev (par);
1155             g_array_free (a, TRUE);
1156
1157     } else
1158         g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
1159 }
1160
1161 /* command parser */
1162 static void
1163 setup_regex() {
1164     uzbl.comm.get_regex  = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
1165             G_REGEX_OPTIMIZE, 0, NULL);
1166     uzbl.comm.set_regex  = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
1167             G_REGEX_OPTIMIZE, 0, NULL);
1168     uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
1169             G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
1170     uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
1171             G_REGEX_OPTIMIZE, 0, NULL);
1172     uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
1173             G_REGEX_OPTIMIZE, 0, NULL);
1174 }
1175
1176 static gboolean
1177 get_var_value(gchar *name) {
1178     uzbl_cmdprop *c;
1179
1180     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1181         if(c->type == TYPE_STR)
1182             printf("VAR: %s VALUE: %s\n", name, (char *)*c->ptr);
1183         else if(c->type == TYPE_INT)
1184             printf("VAR: %s VALUE: %d\n", name, (int)*c->ptr);
1185     }
1186     return TRUE;
1187 }
1188
1189 static void
1190 set_proxy_url() {
1191     SoupURI *suri;
1192
1193     if(*uzbl.net.proxy_url == ' '
1194        || uzbl.net.proxy_url == NULL) {
1195         soup_session_remove_feature_by_type(uzbl.net.soup_session,
1196                 (GType) SOUP_SESSION_PROXY_URI);
1197     }
1198     else {
1199         suri = soup_uri_new(uzbl.net.proxy_url);
1200         g_object_set(G_OBJECT(uzbl.net.soup_session),
1201                 SOUP_SESSION_PROXY_URI,
1202                 suri, NULL);
1203         soup_uri_free(suri);
1204     }
1205     return;
1206 }
1207
1208 static void
1209 cmd_load_uri() {
1210     GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1211     g_array_append_val (a, uzbl.state.uri);
1212     load_uri(uzbl.gui.web_view, a);
1213     g_array_free (a, TRUE);
1214 }
1215
1216 static void 
1217 cmd_always_insert_mode() {
1218     uzbl.behave.insert_mode =
1219         uzbl.behave.always_insert_mode ?  TRUE : FALSE;
1220     update_title();
1221 }
1222
1223 static void
1224 cmd_max_conns() {
1225     g_object_set(G_OBJECT(uzbl.net.soup_session),
1226             SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1227 }
1228
1229 static void
1230 cmd_max_conns_host() {
1231     g_object_set(G_OBJECT(uzbl.net.soup_session),
1232             SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1233 }
1234
1235 static void
1236 cmd_http_debug() {
1237     soup_session_remove_feature
1238         (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1239     /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1240     /*g_free(uzbl.net.soup_logger);*/
1241
1242     uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1243     soup_session_add_feature(uzbl.net.soup_session,
1244             SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1245 }
1246
1247 static WebKitWebSettings*
1248 view_settings() {
1249     return webkit_web_view_get_settings(uzbl.gui.web_view);
1250 }
1251
1252 static void
1253 cmd_font_size() {
1254     WebKitWebSettings *ws = view_settings();
1255     if (uzbl.behave.font_size > 0) {
1256         g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.font_size, NULL);
1257     }
1258     
1259     if (uzbl.behave.monospace_size > 0) {
1260         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1261                       uzbl.behave.monospace_size, NULL);
1262     } else {
1263         g_object_set (G_OBJECT(ws), "default-monospace-font-size",
1264                       uzbl.behave.font_size, NULL);
1265     }
1266 }
1267
1268 static void
1269 cmd_disable_plugins() {
1270     g_object_set (G_OBJECT(view_settings()), "enable-plugins", 
1271             !uzbl.behave.disable_plugins, NULL);
1272 }
1273
1274 static void
1275 cmd_disable_scripts() {
1276     g_object_set (G_OBJECT(view_settings()), "enable-scripts",
1277             !uzbl.behave.disable_plugins, NULL);
1278 }
1279
1280 static void
1281 cmd_minimum_font_size() {
1282     g_object_set (G_OBJECT(view_settings()), "minimum-font-size",
1283             uzbl.behave.minimum_font_size, NULL);
1284 }
1285 static void
1286 cmd_autoload_img() {
1287     g_object_set (G_OBJECT(view_settings()), "auto-load-images",
1288             uzbl.behave.autoload_img, NULL);
1289 }
1290
1291
1292 static void
1293 cmd_autoshrink_img() {
1294     g_object_set (G_OBJECT(view_settings()), "auto-shrink-images",
1295             uzbl.behave.autoshrink_img, NULL);
1296 }
1297
1298
1299 static void
1300 cmd_enable_spellcheck() {
1301     g_object_set (G_OBJECT(view_settings()), "enable-spell-checking",
1302             uzbl.behave.enable_spellcheck, NULL);
1303 }
1304
1305 static void
1306 cmd_enable_private() {
1307     g_object_set (G_OBJECT(view_settings()), "enable-private-browsing",
1308             uzbl.behave.enable_private, NULL);
1309 }
1310
1311 static void
1312 cmd_print_bg() {
1313     g_object_set (G_OBJECT(view_settings()), "print-backgrounds",
1314             uzbl.behave.print_bg, NULL);
1315 }
1316
1317 static void 
1318 cmd_style_uri() {
1319     g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri",
1320             uzbl.behave.style_uri, NULL);
1321 }
1322
1323 static void 
1324 cmd_resizable_txt() {
1325     g_object_set (G_OBJECT(view_settings()), "resizable-text-areas",
1326             uzbl.behave.resizable_txt, NULL);
1327 }
1328
1329 static void 
1330 cmd_default_encoding() {
1331     g_object_set (G_OBJECT(view_settings()), "default-encoding",
1332             uzbl.behave.default_encoding, NULL);
1333 }
1334
1335 static void 
1336 cmd_enforce_96dpi() {
1337     g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi",
1338             uzbl.behave.enforce_96dpi, NULL);
1339 }
1340
1341 static void 
1342 cmd_caret_browsing() {
1343     g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing",
1344             uzbl.behave.caret_browsing, NULL);
1345 }
1346
1347 static void
1348 cmd_cookie_handler() {
1349     gchar **split = g_strsplit(uzbl.behave.cookie_handler, " ", 2);
1350     if ((g_strcmp0(split[0], "sh") == 0) ||
1351         (g_strcmp0(split[0], "spawn") == 0)) {
1352         g_free (uzbl.behave.cookie_handler);
1353         uzbl.behave.cookie_handler =
1354             g_strdup_printf("sync_%s %s", split[0], split[1]);
1355     }
1356     g_strfreev (split);
1357 }
1358
1359 static void
1360 cmd_fifo_dir() {
1361     uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir);
1362 }
1363
1364 static void
1365 cmd_socket_dir() {
1366     uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir);
1367 }
1368
1369 static void
1370 cmd_inject_html() {
1371     if(uzbl.behave.inject_html) {
1372         webkit_web_view_load_html_string (uzbl.gui.web_view,
1373                 uzbl.behave.inject_html, NULL);
1374     }
1375 }
1376
1377 static void
1378 cmd_modkey() {
1379     int i;
1380     char *buf;
1381
1382     buf = g_utf8_strup(uzbl.behave.modkey, -1);
1383     uzbl.behave.modmask = 0;
1384
1385     if(uzbl.behave.modkey) 
1386         g_free(uzbl.behave.modkey);
1387     uzbl.behave.modkey = buf;
1388
1389     for (i = 0; modkeys[i].key != NULL; i++) {
1390         if (g_strrstr(buf, modkeys[i].key))
1391             uzbl.behave.modmask |= modkeys[i].mask;
1392     }
1393 }
1394
1395 static void
1396 cmd_useragent() {
1397     if (*uzbl.net.useragent == ' ') {
1398         g_free (uzbl.net.useragent);
1399         uzbl.net.useragent = NULL;
1400     } else {
1401         gchar *ua = expand_template(uzbl.net.useragent, FALSE);
1402         if (ua)
1403             g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1404         g_free(uzbl.net.useragent);
1405         uzbl.net.useragent = ua;
1406     }
1407 }
1408
1409 static void
1410 move_statusbar() {
1411     gtk_widget_ref(uzbl.gui.scrolled_win);
1412     gtk_widget_ref(uzbl.gui.mainbar);
1413     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1414     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1415
1416     if(uzbl.behave.status_top) {
1417         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1418         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1419     }
1420     else {
1421         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1422         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1423     }
1424     gtk_widget_unref(uzbl.gui.scrolled_win);
1425     gtk_widget_unref(uzbl.gui.mainbar);
1426     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1427     return;
1428 }
1429
1430 static gboolean
1431 set_var_value(gchar *name, gchar *val) {
1432     uzbl_cmdprop *c = NULL;
1433     char *endp = NULL;
1434
1435     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1436         /* check for the variable type */
1437         if (c->type == TYPE_STR) {
1438             g_free(*c->ptr);
1439             *c->ptr = g_strdup(val);
1440         } else if(c->type == TYPE_INT) {
1441             int *ip = (int *)c->ptr;
1442             *ip = (int)strtoul(val, &endp, 10);
1443         }
1444
1445         /* invoke a command specific function */
1446         if(c->func) c->func();
1447     }
1448     return TRUE;
1449 }
1450
1451 static void
1452 runcmd(WebKitWebView* page, GArray *argv) {
1453     (void) page;
1454     parse_cmd_line(argv_idx(argv, 0));
1455 }
1456
1457 static void
1458 render_html() {
1459     Behaviour *b = &uzbl.behave;
1460
1461     if(b->html_buffer->str) {
1462         webkit_web_view_load_html_string (uzbl.gui.web_view,
1463                 b->html_buffer->str, b->base_url);
1464         g_string_free(b->html_buffer, TRUE);
1465         b->html_buffer = g_string_new("");
1466     }
1467 }
1468
1469 enum {M_CMD, M_HTML};
1470 static void
1471 parse_cmd_line(const char *ctl_line) {
1472     gchar **tokens = NULL;
1473     Behaviour *b = &uzbl.behave;
1474
1475     if(b->mode == M_HTML) {
1476         if(!strncmp(b->html_endmarker, ctl_line, strlen(b->html_endmarker))) {
1477             set_timeout(0);
1478             set_var_value("mode", "0");
1479             render_html();
1480             return;
1481         }
1482         else {
1483             set_timeout(b->html_timeout);
1484             g_string_append(b->html_buffer, ctl_line);
1485         }
1486     }
1487     else {
1488         /* SET command */
1489         if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1490             tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1491             if(tokens[0][0] == 0) {
1492                 gchar* value = parseenv(g_strdup(tokens[2]));
1493                 set_var_value(tokens[1], value);
1494                 g_free(value);
1495             }
1496             else
1497                 printf("Error in command: %s\n", tokens[0]);
1498         }
1499         /* GET command */
1500         else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1501             tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1502             if(tokens[0][0] == 0) {
1503                 get_var_value(tokens[1]);
1504             }
1505             else
1506                 printf("Error in command: %s\n", tokens[0]);
1507         }
1508         /* BIND command */
1509         else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1510             tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1511             if(tokens[0][0] == 0) {
1512                 gchar* value = parseenv(g_strdup(tokens[2]));
1513                 add_binding(tokens[1], value);
1514                 g_free(value);
1515             }
1516             else
1517                 printf("Error in command: %s\n", tokens[0]);
1518         }
1519         /* ACT command */
1520         else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1521             tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1522             if(tokens[0][0] == 0) {
1523                 parse_command(tokens[1], tokens[2]);
1524             }
1525             else
1526                 printf("Error in command: %s\n", tokens[0]);
1527         }
1528         /* KEYCMD command */
1529         else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1530             tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1531             if(tokens[0][0] == 0) {
1532                 /* should incremental commands want each individual "keystroke"
1533                    sent in a loop or the whole string in one go like now? */
1534                 g_string_assign(uzbl.state.keycmd, tokens[1]);
1535                 run_keycmd(FALSE);
1536                 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1537                     run_keycmd(TRUE);
1538                 update_title();
1539             }
1540         }
1541         /* Comments */
1542         else if(   (ctl_line[0] == '#')
1543                 || (ctl_line[0] == ' ')
1544                 || (ctl_line[0] == '\n'))
1545             ; /* ignore these lines */
1546         else
1547             printf("Command not understood (%s)\n", ctl_line);
1548
1549         if(tokens)
1550             g_strfreev(tokens);
1551     }
1552
1553     return;
1554 }
1555
1556 static gchar*
1557 build_stream_name(int type, const gchar* dir) {
1558     char *xwin_str;
1559     State *s = &uzbl.state;
1560     gchar *str;
1561
1562     xwin_str = itos((int)uzbl.xwin);
1563     if (type == FIFO) {
1564         str = g_strdup_printf
1565             ("%s/uzbl_fifo_%s", dir,
1566              s->instance_name ? s->instance_name : xwin_str);
1567     } else if (type == SOCKET) {
1568         str = g_strdup_printf
1569             ("%s/uzbl_socket_%s", dir,
1570              s->instance_name ? s->instance_name : xwin_str );
1571     }
1572     g_free(xwin_str);
1573     return str;
1574 }
1575
1576 static gboolean
1577 control_fifo(GIOChannel *gio, GIOCondition condition) {
1578     if (uzbl.state.verbose)
1579         printf("triggered\n");
1580     gchar *ctl_line;
1581     GIOStatus ret;
1582     GError *err = NULL;
1583
1584     if (condition & G_IO_HUP)
1585         g_error ("Fifo: Read end of pipe died!\n");
1586
1587     if(!gio)
1588        g_error ("Fifo: GIOChannel broke\n");
1589
1590     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1591     if (ret == G_IO_STATUS_ERROR) {
1592         g_error ("Fifo: Error reading: %s\n", err->message);
1593         g_error_free (err);
1594     }
1595
1596     parse_cmd_line(ctl_line);
1597     g_free(ctl_line);
1598
1599     return TRUE;
1600 }
1601
1602 static gchar*
1603 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1604     if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1605         if (unlink(uzbl.comm.fifo_path) == -1)
1606             g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1607         g_free(uzbl.comm.fifo_path);
1608         uzbl.comm.fifo_path = NULL;
1609     }
1610
1611     if (*dir == ' ') { /* space unsets the variable */
1612         g_free (dir);
1613         return NULL;
1614     }
1615
1616     GIOChannel *chan = NULL;
1617     GError *error = NULL;
1618     gchar *path = build_stream_name(FIFO, dir);
1619
1620     if (!file_exists(path)) {
1621         if (mkfifo (path, 0666) == 0) {
1622             // we don't really need to write to the file, but if we open the file as 'r' we will block here, waiting for a writer to open the file.
1623             chan = g_io_channel_new_file(path, "r+", &error);
1624             if (chan) {
1625                 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1626                     if (uzbl.state.verbose)
1627                         printf ("init_fifo: created successfully as %s\n", path);
1628                     uzbl.comm.fifo_path = path;
1629                     return dir;
1630                 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1631             } else g_warning ("init_fifo: can't open: %s\n", error->message);
1632         } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1633     } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1634
1635     /* if we got this far, there was an error; cleanup */
1636     if (error) g_error_free (error);
1637     g_free(dir);
1638     g_free(path);
1639     return NULL;
1640 }
1641
1642 static gboolean
1643 control_stdin(GIOChannel *gio, GIOCondition condition) {
1644     (void) condition;
1645     gchar *ctl_line = NULL;
1646     GIOStatus ret;
1647
1648     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1649     if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1650         return FALSE;
1651
1652     parse_cmd_line(ctl_line);
1653     g_free(ctl_line);
1654
1655     return TRUE;
1656 }
1657
1658 static void
1659 create_stdin () {
1660     GIOChannel *chan = NULL;
1661     GError *error = NULL;
1662
1663     chan = g_io_channel_unix_new(fileno(stdin));
1664     if (chan) {
1665         if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1666             g_error ("Stdin: could not add watch\n");
1667         } else {
1668             if (uzbl.state.verbose)
1669                 printf ("Stdin: watch added successfully\n");
1670         }
1671     } else {
1672         g_error ("Stdin: Error while opening: %s\n", error->message);
1673     }
1674     if (error) g_error_free (error);
1675 }
1676
1677 static gboolean
1678 control_socket(GIOChannel *chan) {
1679     struct sockaddr_un remote;
1680     char buffer[512], *ctl_line;
1681     char temp[128];
1682     int sock, clientsock, n, done;
1683     unsigned int t;
1684
1685     sock = g_io_channel_unix_get_fd(chan);
1686
1687     memset (buffer, 0, sizeof (buffer));
1688
1689     t          = sizeof (remote);
1690     clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1691
1692     done = 0;
1693     do {
1694         memset (temp, 0, sizeof (temp));
1695         n = recv (clientsock, temp, 128, 0);
1696         if (n == 0) {
1697             buffer[strlen (buffer)] = '\0';
1698             done = 1;
1699         }
1700         if (!done)
1701             strcat (buffer, temp);
1702     } while (!done);
1703
1704     if (strcmp (buffer, "\n") < 0) {
1705         buffer[strlen (buffer) - 1] = '\0';
1706     } else {
1707         buffer[strlen (buffer)] = '\0';
1708     }
1709     close (clientsock);
1710     ctl_line = g_strdup(buffer);
1711     parse_cmd_line (ctl_line);
1712
1713 /*
1714    TODO: we should be able to do it with this.  but glib errors out with "Invalid argument"
1715     GError *error = NULL;
1716     gsize len;
1717     GIOStatus ret;
1718     ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1719     if (ret == G_IO_STATUS_ERROR)
1720         g_error ("Error reading: %s\n", error->message);
1721
1722     printf("Got line %s (%u bytes) \n",ctl_line, len);
1723     if(ctl_line) {
1724        parse_line(ctl_line);
1725 */
1726
1727     g_free(ctl_line);
1728     return TRUE;
1729 }
1730
1731 static gchar*
1732 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1733     if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1734         if (unlink(uzbl.comm.socket_path) == -1)
1735             g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1736         g_free(uzbl.comm.socket_path);
1737         uzbl.comm.socket_path = NULL;
1738     }
1739
1740     if (*dir == ' ') {
1741         g_free(dir);
1742         return NULL;
1743     }
1744
1745     GIOChannel *chan = NULL;
1746     int sock, len;
1747     struct sockaddr_un local;
1748     gchar *path = build_stream_name(SOCKET, dir);
1749
1750     sock = socket (AF_UNIX, SOCK_STREAM, 0);
1751
1752     local.sun_family = AF_UNIX;
1753     strcpy (local.sun_path, path);
1754     unlink (local.sun_path);
1755
1756     len = strlen (local.sun_path) + sizeof (local.sun_family);
1757     if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1758         if (uzbl.state.verbose)
1759             printf ("init_socket: opened in %s\n", path);
1760         listen (sock, 5);
1761
1762         if( (chan = g_io_channel_unix_new(sock)) ) {
1763             g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1764             uzbl.comm.socket_path = path;
1765             return dir;
1766         }
1767     } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1768
1769     /* if we got this far, there was an error; cleanup */
1770     g_free(path);
1771     g_free(dir);
1772     return NULL;
1773 }
1774
1775 /*
1776  NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1777  it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1778 */
1779 // this function may be called very early when the templates are not set (yet), hence the checks
1780 static void
1781 update_title (void) {
1782     Behaviour *b = &uzbl.behave;
1783     gchar *parsed;
1784
1785     if (b->show_status) {
1786         if (b->title_format_short) {
1787             parsed = expand_template(b->title_format_short, FALSE);
1788             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1789             g_free(parsed);
1790         }
1791         if (b->status_format) {
1792             parsed = expand_template(b->status_format, TRUE);
1793             gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1794             g_free(parsed);
1795         }
1796         if (b->status_background) {
1797             GdkColor color;
1798             gdk_color_parse (b->status_background, &color);
1799             //labels and hboxes do not draw their own background.  applying this on the window is ok as we the statusbar is the only affected widget.  (if not, we could also use GtkEventBox)
1800             gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1801         }
1802     } else {
1803         if (b->title_format_long) {
1804             parsed = expand_template(b->title_format_long, FALSE);
1805             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1806             g_free(parsed);
1807         }
1808     }
1809 }
1810
1811 static gboolean
1812 key_press_cb (GtkWidget* window, GdkEventKey* event)
1813 {
1814     //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1815
1816     (void) window;
1817
1818     if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1819         || event->keyval == GDK_Up || event->keyval == GDK_Down || event->keyval == GDK_Left || event->keyval == GDK_Right || event->keyval == GDK_Shift_L || event->keyval == GDK_Shift_R)
1820         return FALSE;
1821
1822     /* turn off insert mode (if always_insert_mode is not used) */
1823     if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1824         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1825         update_title();
1826         return TRUE;
1827     }
1828
1829     if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1830         return FALSE;
1831
1832     if (event->keyval == GDK_Escape) {
1833         g_string_truncate(uzbl.state.keycmd, 0);
1834         update_title();
1835         dehilight(uzbl.gui.web_view, NULL);
1836         return TRUE;
1837     }
1838
1839     //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1840     if (event->keyval == GDK_Insert) {
1841         gchar * str;
1842         if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1843             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1844         } else {
1845             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1846         }
1847         if (str) {
1848             g_string_append (uzbl.state.keycmd, str);
1849             update_title ();
1850             g_free (str);
1851         }
1852         return TRUE;
1853     }
1854
1855     if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1856         g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1857         update_title();
1858     }
1859
1860     gboolean key_ret = FALSE;
1861     if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1862         key_ret = TRUE;
1863     if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1864
1865     run_keycmd(key_ret);
1866     update_title();
1867     if (key_ret) return (!uzbl.behave.insert_mode);
1868     return TRUE;
1869 }
1870
1871 static void
1872 run_keycmd(const gboolean key_ret) {
1873     /* run the keycmd immediately if it isn't incremental and doesn't take args */
1874     Action *act;
1875     if ((act = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1876         g_string_truncate(uzbl.state.keycmd, 0);
1877         parse_command(act->name, act->param);
1878         return;
1879     }
1880
1881     /* try if it's an incremental keycmd or one that takes args, and run it */
1882     GString* short_keys = g_string_new ("");
1883     GString* short_keys_inc = g_string_new ("");
1884     guint i;
1885     for (i=0; i<(uzbl.state.keycmd->len); i++) {
1886         g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1887         g_string_assign(short_keys_inc, short_keys->str);
1888         g_string_append_c(short_keys, '_');
1889         g_string_append_c(short_keys_inc, '*');
1890
1891         if (key_ret && (act = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1892             /* run normal cmds only if return was pressed */
1893             exec_paramcmd(act, i);
1894             g_string_truncate(uzbl.state.keycmd, 0);
1895             break;
1896         } else if ((act = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1897             if (key_ret)  /* just quit the incremental command on return */
1898                 g_string_truncate(uzbl.state.keycmd, 0);
1899             else exec_paramcmd(act, i); /* otherwise execute the incremental */
1900             break;
1901         }
1902         
1903         g_string_truncate(short_keys, short_keys->len - 1);
1904     }
1905     g_string_free (short_keys, TRUE);
1906     g_string_free (short_keys_inc, TRUE);
1907 }
1908
1909 static void
1910 exec_paramcmd(const Action *act, const guint i) {
1911     GString *parampart = g_string_new (uzbl.state.keycmd->str);
1912     GString *actionname = g_string_new ("");
1913     GString *actionparam = g_string_new ("");
1914     g_string_erase (parampart, 0, i+1);
1915     if (act->name)
1916         g_string_printf (actionname, act->name, parampart->str);
1917     if (act->param)
1918         g_string_printf (actionparam, act->param, parampart->str);
1919     parse_command(actionname->str, actionparam->str);
1920     g_string_free(actionname, TRUE);
1921     g_string_free(actionparam, TRUE);
1922     g_string_free(parampart, TRUE);
1923 }
1924
1925
1926 static GtkWidget*
1927 create_browser () {
1928     GUI *g = &uzbl.gui;
1929
1930     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1931     //main_window_ref = g_object_ref(scrolled_window);
1932     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_NEVER); //todo: some sort of display of position/total length. like what emacs does
1933
1934     g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1935     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1936
1937     g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1938     g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1939     g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1940     g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1941     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1942     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1943     g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1944     g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1945     g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1946     g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1947
1948     return scrolled_window;
1949 }
1950
1951 static GtkWidget*
1952 create_mainbar () {
1953     GUI *g = &uzbl.gui;
1954
1955     g->mainbar = gtk_hbox_new (FALSE, 0);
1956
1957     /* keep a reference to the bar so we can re-pack it at runtime*/
1958     //sbar_ref = g_object_ref(g->mainbar);
1959
1960     g->mainbar_label = gtk_label_new ("");
1961     gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1962     gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1963     gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1964     gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1965     gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1966     return g->mainbar;
1967 }
1968
1969 static
1970 GtkWidget* create_window () {
1971     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1972     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1973     gtk_widget_set_name (window, "Uzbl browser");
1974     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1975     g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);
1976
1977     return window;
1978 }
1979
1980 static void
1981 run_handler (const gchar *act, const gchar *args) {
1982     char **parts = g_strsplit(act, " ", 2);
1983     if (!parts) return;
1984     else if ((g_strcmp0(parts[0], "spawn") == 0)
1985              || (g_strcmp0(parts[0], "sh") == 0)
1986              || (g_strcmp0(parts[0], "sync_spawn") == 0)
1987              || (g_strcmp0(parts[0], "sync_sh") == 0)) {
1988         guint i;
1989         GString *a = g_string_new ("");
1990         char **spawnparts;
1991         spawnparts = split_quoted(parts[1], FALSE);
1992         g_string_append_printf(a, "%s", spawnparts[0]);
1993         if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1994         
1995         for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1996             g_string_append_printf(a, " %s", spawnparts[i]);
1997         parse_command(parts[0], a->str);
1998         g_string_free (a, TRUE);
1999         g_strfreev (spawnparts);
2000     } else
2001         parse_command(parts[0], parts[1]);
2002     g_strfreev (parts);
2003 }
2004
2005 static void
2006 add_binding (const gchar *key, const gchar *act) {
2007     char **parts = g_strsplit(act, " ", 2);
2008     Action *action;
2009
2010     if (!parts)
2011         return;
2012
2013     //Debug:
2014     if (uzbl.state.verbose)
2015         printf ("Binding %-10s : %s\n", key, act);
2016     action = new_action(parts[0], parts[1]);
2017
2018     g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
2019     g_strfreev(parts);
2020 }
2021
2022 static gchar*
2023 get_xdg_var (XDG_Var xdg) {
2024     const gchar* actual_value = getenv (xdg.environmental);
2025     const gchar* home         = getenv ("HOME");
2026
2027     gchar* return_value = str_replace ("~", home, actual_value);
2028
2029     if (! actual_value || strcmp (actual_value, "") == 0) {
2030         if (xdg.default_value) {
2031             return_value = str_replace ("~", home, xdg.default_value);
2032         } else {
2033             return_value = NULL;
2034         }
2035     }
2036     return return_value;
2037 }
2038
2039 static gchar*
2040 find_xdg_file (int xdg_type, char* filename) {
2041     /* xdg_type = 0 => config
2042        xdg_type = 1 => data
2043        xdg_type = 2 => cache*/
2044
2045     gchar* xdgv = get_xdg_var (XDG[xdg_type]);
2046     gchar* temporary_file = g_strconcat (xdgv, filename, NULL);
2047     g_free (xdgv);
2048
2049     gchar* temporary_string;
2050     char*  saveptr;
2051     char*  buf;
2052
2053     if (! file_exists (temporary_file) && xdg_type != 2) {
2054         buf = get_xdg_var (XDG[3 + xdg_type]);
2055         temporary_string = (char *) strtok_r (buf, ":", &saveptr);
2056         g_free(buf);
2057
2058         while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) {
2059             g_free (temporary_file);
2060             temporary_file = g_strconcat (temporary_string, filename, NULL);
2061         }
2062     }
2063     
2064     //g_free (temporary_string); - segfaults.
2065
2066     if (file_exists (temporary_file)) {
2067         return temporary_file;
2068     } else {
2069         return NULL;
2070     }
2071 }
2072 static void
2073 settings_init () {
2074     State *s = &uzbl.state;
2075     Network *n = &uzbl.net;
2076     int i;
2077     for (i = 0; default_config[i].command != NULL; i++) {
2078         parse_cmd_line(default_config[i].command);
2079     }
2080
2081     if (!s->config_file) {
2082         s->config_file = find_xdg_file (0, "/uzbl/config");
2083     }
2084
2085     if (s->config_file) {
2086         GArray* lines = read_file_by_line (s->config_file);
2087         int i = 0;
2088         gchar* line;
2089
2090         while ((line = g_array_index(lines, gchar*, i))) {
2091             parse_cmd_line (line);
2092             i ++;
2093             g_free (line);
2094         }
2095         g_array_free (lines, TRUE);
2096     } else {
2097         if (uzbl.state.verbose)
2098             printf ("No configuration file loaded.\n");
2099     }
2100
2101     g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
2102 }
2103
2104 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
2105     (void) session;
2106     (void) user_data;
2107     if (!uzbl.behave.cookie_handler) return;
2108
2109     soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
2110     GString *s = g_string_new ("");
2111     SoupURI * soup_uri = soup_message_get_uri(msg);
2112     g_string_printf(s, "GET '%s' '%s'", soup_uri->host, soup_uri->path);
2113     run_handler(uzbl.behave.cookie_handler, s->str);
2114
2115     if(uzbl.comm.sync_stdout)
2116         soup_message_headers_replace (msg->request_headers, "Cookie", uzbl.comm.sync_stdout);
2117     //printf("stdout: %s\n", uzbl.comm.sync_stdout);   // debugging
2118     if (uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout);
2119         
2120     g_string_free(s, TRUE);
2121 }
2122
2123 static void
2124 save_cookies (SoupMessage *msg, gpointer user_data){
2125     (void) user_data;
2126     GSList *ck;
2127     char *cookie;
2128     for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
2129         cookie = soup_cookie_to_set_cookie_header(ck->data);
2130         SoupURI * soup_uri = soup_message_get_uri(msg);
2131         GString *s = g_string_new ("");
2132         g_string_printf(s, "PUT '%s' '%s' '%s'", soup_uri->host, soup_uri->path, cookie);
2133         run_handler(uzbl.behave.cookie_handler, s->str);
2134         g_free (cookie);
2135         g_string_free(s, TRUE);
2136     }
2137     g_slist_free(ck);
2138 }
2139
2140 /* --- WEBINSPECTOR --- */
2141 static void
2142 hide_window_cb(GtkWidget *widget, gpointer data) {
2143     (void) data;
2144
2145     gtk_widget_hide(widget);
2146 }
2147
2148 static WebKitWebView*
2149 create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
2150     (void) data;
2151     (void) page;
2152     (void) web_inspector;
2153     GtkWidget* scrolled_window;
2154     GtkWidget* new_web_view;
2155     GUI *g = &uzbl.gui;
2156
2157     g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2158     g_signal_connect(G_OBJECT(g->inspector_window), "delete-event",
2159             G_CALLBACK(hide_window_cb), NULL);
2160
2161     gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector");
2162     gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300);
2163     gtk_widget_show(g->inspector_window);
2164
2165     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2166     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2167             GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2168     gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window);
2169     gtk_widget_show(scrolled_window);
2170
2171     new_web_view = webkit_web_view_new();
2172     gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view);
2173
2174     return WEBKIT_WEB_VIEW(new_web_view);
2175 }
2176
2177 static gboolean
2178 inspector_show_window_cb (WebKitWebInspector* inspector){
2179     (void) inspector;
2180     gtk_widget_show(uzbl.gui.inspector_window);
2181     return TRUE;
2182 }
2183
2184 /* TODO: Add variables and code to make use of these functions */
2185 static gboolean
2186 inspector_close_window_cb (WebKitWebInspector* inspector){
2187     (void) inspector;
2188     return TRUE;
2189 }
2190
2191 static gboolean
2192 inspector_attach_window_cb (WebKitWebInspector* inspector){
2193     (void) inspector;
2194     return FALSE;
2195 }
2196
2197 static gboolean
2198 inspector_dettach_window_cb (WebKitWebInspector* inspector){
2199     (void) inspector;
2200     return FALSE;
2201 }
2202
2203 static gboolean
2204 inspector_uri_changed_cb (WebKitWebInspector* inspector){
2205     (void) inspector;
2206     return FALSE;
2207 }
2208
2209 static gboolean
2210 inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
2211     (void) inspector;
2212     return FALSE;
2213 }
2214
2215 static void
2216 set_up_inspector() {
2217     GUI *g = &uzbl.gui;
2218     WebKitWebSettings *settings = view_settings();
2219     g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);
2220
2221     uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view);
2222     g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL);
2223     g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL);
2224     g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL);
2225     g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL);
2226     g_signal_connect (G_OBJECT (g->inspector), "dettach-window", G_CALLBACK (inspector_dettach_window_cb), NULL);
2227     g_signal_connect (G_OBJECT (g->inspector), "destroy", G_CALLBACK (inspector_inspector_destroyed_cb), NULL);
2228
2229     g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL);
2230 }
2231
2232 static void
2233 dump_var_hash(gpointer k, gpointer v, gpointer ud) {
2234     (void) ud;
2235     uzbl_cmdprop *c = v;
2236
2237     if(!c->dump)
2238         return;
2239
2240     if(c->type == TYPE_STR)
2241         printf("set %s = %s\n", (char *)k, *c->ptr?(char *)*c->ptr:" ");
2242     else if(c->type == TYPE_INT)
2243         printf("set %s = %d\n", (char *)k, (int)*c->ptr);
2244 }
2245
2246 static void
2247 dump_key_hash(gpointer k, gpointer v, gpointer ud) {
2248     (void) ud;
2249     Action *a = v;
2250
2251     printf("bind %s = %s %s\n", (char *)k ,
2252             (char *)a->name, a->param?(char *)a->param:"");
2253 }
2254
2255 static void
2256 dump_config() {
2257     g_hash_table_foreach(uzbl.comm.proto_var, dump_var_hash, NULL);
2258     g_hash_table_foreach(uzbl.bindings, dump_key_hash, NULL);
2259 }
2260
2261 /** -- MAIN -- **/
2262 int
2263 main (int argc, char* argv[]) {
2264     gtk_init (&argc, &argv);
2265     if (!g_thread_supported ())
2266         g_thread_init (NULL);
2267     uzbl.state.executable_path = g_strdup(argv[0]);
2268     uzbl.state.selected_url = NULL;
2269     uzbl.state.searchtx = NULL;
2270
2271     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
2272     g_option_context_add_main_entries (context, entries, NULL);
2273     g_option_context_add_group (context, gtk_get_option_group (TRUE));
2274     g_option_context_parse (context, &argc, &argv, NULL);
2275     g_option_context_free(context);
2276     /* initialize hash table */
2277     uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
2278
2279     uzbl.net.soup_session = webkit_get_default_session();
2280     uzbl.state.keycmd = g_string_new("");
2281
2282     if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
2283         fprintf(stderr, "uzbl: error hooking SIGTERM\n");
2284     if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
2285         fprintf(stderr, "uzbl: error hooking SIGINT\n");
2286     if(setup_signal(SIGALRM, catch_alrm) == SIG_ERR)
2287         fprintf(stderr, "uzbl: error hooking SIGALARM\n");
2288
2289
2290     if(uname(&uzbl.state.unameinfo) == -1)
2291         g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");
2292
2293     uzbl.gui.sbar.progress_s = g_strdup("=");
2294     uzbl.gui.sbar.progress_u = g_strdup("ยท");
2295     uzbl.gui.sbar.progress_w = 10;
2296
2297     /* HTML mode defaults*/
2298     uzbl.behave.html_buffer = g_string_new("");
2299     uzbl.behave.html_endmarker = g_strdup(".");
2300     uzbl.behave.html_timeout = 60;
2301     uzbl.behave.base_url = g_strdup("http://invalid");
2302
2303     /* default mode indicators */
2304     uzbl.behave.insert_indicator = g_strdup("I");
2305     uzbl.behave.cmd_indicator    = g_strdup("C");
2306
2307     setup_regex();
2308     setup_scanner();
2309     commands_hash ();
2310     make_var_to_name_hash();
2311
2312     uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
2313
2314     uzbl.gui.scrolled_win = create_browser();
2315     create_mainbar();
2316
2317     /* initial packing */
2318     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
2319     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
2320
2321     uzbl.gui.main_window = create_window ();
2322     gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
2323
2324
2325     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
2326     gtk_widget_show_all (uzbl.gui.main_window);
2327     uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
2328
2329     if (uzbl.state.verbose) {
2330         printf("Uzbl start location: %s\n", argv[0]);
2331         printf("window_id %i\n",(int) uzbl.xwin);
2332         printf("pid %i\n", getpid ());
2333         printf("name: %s\n", uzbl.state.instance_name);
2334     }
2335
2336     uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
2337     uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
2338     uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
2339     uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
2340     gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
2341
2342     settings_init ();
2343
2344     if (!uzbl.behave.show_status)
2345         gtk_widget_hide(uzbl.gui.mainbar);
2346     else
2347         update_title();
2348
2349     /* WebInspector */
2350     set_up_inspector();
2351
2352     create_stdin();
2353
2354     if(uzbl.state.uri) {
2355         GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
2356         g_array_append_val(a, uzbl.state.uri);
2357         load_uri (uzbl.gui.web_view, a);
2358         g_array_free (a, TRUE);
2359     }
2360
2361     gtk_main ();
2362     clean_up();
2363
2364     return EXIT_SUCCESS;
2365 }
2366
2367 /* vi: set et ts=4: */