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