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