Merge commit 'rob/master' into experimental
[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     void (*func)(void);
85 } uzbl_cmdprop;
86
87 enum {TYPE_INT, TYPE_STR};
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     { "disable_plugins",     PTR(uzbl.behave.disable_plugins,     INT, cmd_disable_plugins)},
137     { "disable_scripts",     PTR(uzbl.behave.disable_scripts,     INT, cmd_disable_scripts)},
138     { "autoload_images",     PTR(uzbl.behave.autoload_img,        INT, cmd_autoload_img)},
139     { "autoshrink_images",   PTR(uzbl.behave.autoshrink_img,      INT, cmd_autoshrink_img)},
140     { "enable_spellcheck",   PTR(uzbl.behave.enable_spellcheck,   INT, cmd_enable_spellcheck)},
141     { "enable_private",      PTR(uzbl.behave.enable_private,      INT, cmd_enable_private)},
142     { "print_backgrounds",   PTR(uzbl.behave.print_bg,            INT, cmd_print_bg)},
143     { "stylesheet_uri",      PTR(uzbl.behave.style_uri,           STR, cmd_style_uri)},
144     { "resizable_text_areas",PTR(uzbl.behave.resizable_txt,       INT, cmd_resizable_txt)},
145     { "default_encoding",    PTR(uzbl.behave.default_encoding,    STR, cmd_default_encoding)},
146     { "enforce_96_dpi",      PTR(uzbl.behave.enforce_96dpi,       INT, cmd_enforce_96dpi)},
147     { "caret_browsing",      PTR(uzbl.behave.caret_browsing,      INT, cmd_caret_browsing)},
148
149     { NULL,                  {.ptr = NULL, .type = TYPE_INT, .func = NULL}}
150 }, *n2v_p = var_name_to_ptr;
151
152 const struct {
153     char *key;
154     guint mask;
155 } modkeys[] = {
156     { "SHIFT",   GDK_SHIFT_MASK   }, // shift
157     { "LOCK",    GDK_LOCK_MASK    }, // capslock or shiftlock, depending on xserver's modmappings
158     { "CONTROL", GDK_CONTROL_MASK }, // control
159     { "MOD1",    GDK_MOD1_MASK    }, // 4th mod - normally alt but depends on modmappings
160     { "MOD2",    GDK_MOD2_MASK    }, // 5th mod
161     { "MOD3",    GDK_MOD3_MASK    }, // 6th mod
162     { "MOD4",    GDK_MOD4_MASK    }, // 7th mod
163     { "MOD5",    GDK_MOD5_MASK    }, // 8th mod
164     { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
165     { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
166     { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
167     { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
168     { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
169     { "SUPER",   GDK_SUPER_MASK   }, // super (since 2.10)
170     { "HYPER",   GDK_HYPER_MASK   }, // hyper (since 2.10)
171     { "META",    GDK_META_MASK    }, // meta (since 2.10)
172     { NULL,      0                }
173 };
174
175
176 /* construct a hash from the var_name_to_ptr array for quick access */
177 static void
178 make_var_to_name_hash() {
179     uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
180     while(n2v_p->name) {
181         g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
182         n2v_p++;
183     }
184 }
185
186
187 /* --- UTILITY FUNCTIONS --- */
188
189 char *
190 itos(int val) {
191     char tmp[20];
192
193     snprintf(tmp, sizeof(tmp), "%i", val);
194     return g_strdup(tmp);
195 }
196
197 static gchar*
198 strfree(gchar *str) { g_free(str); return NULL; }  // for freeing & setting to null in one go
199
200 static gchar*
201 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
202
203 static char *
204 str_replace (const char* search, const char* replace, const char* string) {
205     gchar **buf;
206     char *ret;
207
208     buf = g_strsplit (string, search, -1);
209     ret = g_strjoinv (replace, buf);
210     g_strfreev(buf); // somebody said this segfaults
211
212     return ret;
213 }
214
215 static GArray*
216 read_file_by_line (gchar *path) {
217     GIOChannel *chan = NULL;
218     gchar *readbuf = NULL;
219     gsize len;
220     GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
221     int i = 0;
222     
223     chan = g_io_channel_new_file(path, "r", NULL);
224     
225     if (chan) {
226         while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
227             const gchar* val = g_strdup (readbuf);
228             g_array_append_val (lines, val);
229             g_free (readbuf);
230             i ++;
231         }
232         
233         g_io_channel_unref (chan);
234     } else {
235         fprintf(stderr, "File '%s' not be read.\n", path);
236     }
237     
238     return lines;
239 }
240
241 static
242 gchar* parseenv (char* string) {
243     extern char** environ;
244     gchar* tmpstr = NULL;
245     int i = 0;
246     
247
248     while (environ[i] != NULL) {
249         gchar** env = g_strsplit (environ[i], "=", 2);
250         gchar* envname = g_strconcat ("$", env[0], NULL);
251
252         if (g_strrstr (string, envname) != NULL) {
253             tmpstr = g_strdup(string);
254             g_free (string);
255             string = str_replace(envname, env[1], tmpstr);
256             g_free (tmpstr);
257         }
258
259         g_free (envname);
260         g_strfreev (env); // somebody said this breaks uzbl
261         i++;
262     }
263
264     return string;
265 }
266
267 static sigfunc*
268 setup_signal(int signr, sigfunc *shandler) {
269     struct sigaction nh, oh;
270
271     nh.sa_handler = shandler;
272     sigemptyset(&nh.sa_mask);
273     nh.sa_flags = 0;
274
275     if(sigaction(signr, &nh, &oh) < 0)
276         return SIG_ERR;
277
278     return NULL;
279 }
280
281 static void
282 clean_up(void) {
283     if (uzbl.behave.fifo_dir)
284         unlink (uzbl.comm.fifo_path);
285     if (uzbl.behave.socket_dir)
286         unlink (uzbl.comm.socket_path);
287
288     g_free(uzbl.state.executable_path);
289     g_string_free(uzbl.state.keycmd, TRUE);
290     g_hash_table_destroy(uzbl.bindings);
291     g_hash_table_destroy(uzbl.behave.commands);
292 }
293
294 /* used for html_mode_timeout 
295  * be sure to extend this function to use
296  * more timers if needed in other places
297 */
298 static void
299 set_timeout(int seconds) {
300     struct itimerval t;
301     memset(&t, 0, sizeof t);
302
303     t.it_value.tv_sec =  seconds;
304     t.it_value.tv_usec = 0;
305     setitimer(ITIMER_REAL, &t, NULL);
306 }
307
308 /* --- SIGNAL HANDLER --- */
309
310 static void
311 catch_sigterm(int s) {
312     (void) s;
313     clean_up();
314 }
315
316 static void
317 catch_sigint(int s) {
318     (void) s;
319     clean_up();
320     exit(EXIT_SUCCESS);
321 }
322
323 static void
324 catch_alrm(int s) {
325     (void) s;
326
327     set_var_value("mode", "0");
328     render_html();
329 }
330
331
332 /* --- CALLBACKS --- */
333
334 static gboolean
335 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
336     (void) web_view;
337     (void) frame;
338     (void) navigation_action;
339     (void) policy_decision;
340     (void) user_data;
341     const gchar* uri = webkit_network_request_get_uri (request);
342     if (uzbl.state.verbose)
343         printf("New window requested -> %s \n", uri);
344     new_window_load_uri(uri);
345     return (FALSE);
346 }
347
348 WebKitWebView*
349 create_web_view_cb (WebKitWebView  *web_view, WebKitWebFrame *frame, gpointer user_data) {
350     (void) web_view;
351     (void) frame;
352     (void) user_data;
353     if (uzbl.state.selected_url != NULL) {
354         if (uzbl.state.verbose)
355             printf("\nNew web view -> %s\n",uzbl.state.selected_url);
356         new_window_load_uri(uzbl.state.selected_url);
357     } else {
358         if (uzbl.state.verbose)
359             printf("New web view -> %s\n","Nothing to open, exiting");
360     }
361     return (NULL);
362 }
363
364 static gboolean
365 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
366     (void) web_view;
367     (void) user_data;
368     if (uzbl.behave.download_handler) {
369         const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
370         if (uzbl.state.verbose)
371             printf("Download -> %s\n",uri);
372         /* if urls not escaped, we may have to escape and quote uri before this call */
373         run_handler(uzbl.behave.download_handler, uri);
374     }
375     return (FALSE);
376 }
377
378 /* scroll a bar in a given direction */
379 static void
380 scroll (GtkAdjustment* bar, GArray *argv) {
381     gdouble amount;
382     gchar *end;
383
384     amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
385     if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
386     gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
387 }
388
389 static void scroll_begin(WebKitWebView* page, GArray *argv) {
390     (void) page; (void) argv;
391     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
392 }
393
394 static void scroll_end(WebKitWebView* page, GArray *argv) {
395     (void) page; (void) argv;
396     gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
397                               gtk_adjustment_get_page_size(uzbl.gui.bar_v));
398 }
399
400 static void scroll_vert(WebKitWebView* page, GArray *argv) {
401     (void) page;
402     scroll(uzbl.gui.bar_v, argv);
403 }
404
405 static void scroll_horz(WebKitWebView* page, GArray *argv) {
406     (void) page;
407     scroll(uzbl.gui.bar_h, argv);
408 }
409
410 static void
411 cmd_set_status() {
412     if (!uzbl.behave.show_status) {
413         gtk_widget_hide(uzbl.gui.mainbar);
414     } else {
415         gtk_widget_show(uzbl.gui.mainbar);
416     }
417     update_title();
418 }
419
420 static void
421 toggle_status_cb (WebKitWebView* page, GArray *argv) {
422     (void)page;
423     (void)argv;
424
425     if (uzbl.behave.show_status) {
426         gtk_widget_hide(uzbl.gui.mainbar);
427     } else {
428         gtk_widget_show(uzbl.gui.mainbar);
429     }
430     uzbl.behave.show_status = !uzbl.behave.show_status;
431     update_title();
432 }
433
434 static void
435 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
436     (void) page;
437     (void) title;
438     (void) data;
439     //Set selected_url state variable
440     g_free(uzbl.state.selected_url);
441     uzbl.state.selected_url = NULL;
442     if (link) {
443         uzbl.state.selected_url = g_strdup(link);
444     }
445     update_title();
446 }
447
448 static void
449 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
450     (void) web_view;
451     (void) web_frame;
452     (void) data;
453     if (uzbl.gui.main_title)
454         g_free (uzbl.gui.main_title);
455     uzbl.gui.main_title = g_strdup (title);
456     update_title();
457 }
458
459 static void
460 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
461     (void) page;
462     (void) data;
463     uzbl.gui.sbar.load_progress = progress;
464     update_title();
465 }
466
467 static void
468 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
469     (void) page;
470     (void) frame;
471     (void) data;
472     if (uzbl.behave.load_finish_handler)
473         run_handler(uzbl.behave.load_finish_handler, "");
474 }
475
476 static void
477 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
478     (void) page;
479     (void) frame;
480     (void) data;
481     g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
482     if (uzbl.behave.load_start_handler)
483         run_handler(uzbl.behave.load_start_handler, "");
484 }
485
486 static void
487 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
488     (void) page;
489     (void) data;
490     g_free (uzbl.state.uri);
491     GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
492     uzbl.state.uri = g_string_free (newuri, FALSE);
493     if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
494         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
495         update_title();
496     }
497     if (uzbl.behave.load_commit_handler)
498         run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
499 }
500
501 static void
502 destroy_cb (GtkWidget* widget, gpointer data) {
503     (void) widget;
504     (void) data;
505     gtk_main_quit ();
506 }
507
508 static void
509 log_history_cb () {
510    if (uzbl.behave.history_handler) {
511        time_t rawtime;
512        struct tm * timeinfo;
513        char date [80];
514        time ( &rawtime );
515        timeinfo = localtime ( &rawtime );
516        strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
517        run_handler(uzbl.behave.history_handler, date);
518    }
519 }
520
521
522 /* VIEW funcs (little webkit wrappers) */
523 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
524 VIEWFUNC(reload)
525 VIEWFUNC(reload_bypass_cache)
526 VIEWFUNC(stop_loading)
527 VIEWFUNC(zoom_in)
528 VIEWFUNC(zoom_out)
529 VIEWFUNC(go_back)
530 VIEWFUNC(go_forward)
531 #undef VIEWFUNC
532
533 /* -- command to callback/function map for things we cannot attach to any signals */
534 // TODO: reload
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_disable_plugins() {
1256     g_object_set (G_OBJECT(view_settings()), "enable-plugins", 
1257             !uzbl.behave.disable_plugins, NULL);
1258 }
1259
1260 static void
1261 cmd_disable_scripts() {
1262     g_object_set (G_OBJECT(view_settings()), "enable-scripts",
1263             !uzbl.behave.disable_plugins, NULL);
1264 }
1265
1266 static void
1267 cmd_minimum_font_size() {
1268     g_object_set (G_OBJECT(view_settings()), "minimum-font-size",
1269             uzbl.behave.minimum_font_size, NULL);
1270 }
1271 static void
1272 cmd_autoload_img() {
1273     g_object_set (G_OBJECT(view_settings()), "auto-load-images",
1274             uzbl.behave.autoload_img, NULL);
1275 }
1276
1277
1278 static void
1279 cmd_autoshrink_img() {
1280     g_object_set (G_OBJECT(view_settings()), "auto-shrink-images",
1281             uzbl.behave.autoshrink_img, NULL);
1282 }
1283
1284
1285 static void
1286 cmd_enable_spellcheck() {
1287     g_object_set (G_OBJECT(view_settings()), "enable-spell-checking",
1288             uzbl.behave.enable_spellcheck, NULL);
1289 }
1290
1291 static void
1292 cmd_enable_private() {
1293     g_object_set (G_OBJECT(view_settings()), "enable-private-browsing",
1294             uzbl.behave.enable_private, NULL);
1295 }
1296
1297 static void
1298 cmd_print_bg() {
1299     g_object_set (G_OBJECT(view_settings()), "print-backgrounds",
1300             uzbl.behave.print_bg, NULL);
1301 }
1302
1303 static void 
1304 cmd_style_uri() {
1305     g_object_set (G_OBJECT(view_settings()), "user-stylesheet-uri",
1306             uzbl.behave.style_uri, NULL);
1307 }
1308
1309 static void 
1310 cmd_resizable_txt() {
1311     g_object_set (G_OBJECT(view_settings()), "resizable-text-areas",
1312             uzbl.behave.resizable_txt, NULL);
1313 }
1314
1315 static void 
1316 cmd_default_encoding() {
1317     g_object_set (G_OBJECT(view_settings()), "default-encoding",
1318             uzbl.behave.default_encoding, NULL);
1319 }
1320
1321 static void 
1322 cmd_enforce_96dpi() {
1323     g_object_set (G_OBJECT(view_settings()), "enforce-96-dpi",
1324             uzbl.behave.enforce_96dpi, NULL);
1325 }
1326
1327 static void 
1328 cmd_caret_browsing() {
1329     g_object_set (G_OBJECT(view_settings()), "enable-caret-browsing",
1330             uzbl.behave.caret_browsing, NULL);
1331 }
1332
1333 static void
1334 cmd_cookie_handler() {
1335     gchar **split = g_strsplit(uzbl.behave.cookie_handler, " ", 2);
1336     if ((g_strcmp0(split[0], "sh") == 0) ||
1337         (g_strcmp0(split[0], "spawn") == 0)) {
1338         g_free (uzbl.behave.cookie_handler);
1339         uzbl.behave.cookie_handler =
1340             g_strdup_printf("sync_%s %s", split[0], split[1]);
1341     }
1342     g_strfreev (split);
1343 }
1344
1345 static void
1346 cmd_fifo_dir() {
1347     uzbl.behave.fifo_dir = init_fifo(uzbl.behave.fifo_dir);
1348 }
1349
1350 static void
1351 cmd_socket_dir() {
1352     uzbl.behave.socket_dir = init_socket(uzbl.behave.socket_dir);
1353 }
1354
1355 static void
1356 cmd_inject_html() {
1357     if(uzbl.behave.inject_html) {
1358         webkit_web_view_load_html_string (uzbl.gui.web_view,
1359                 uzbl.behave.inject_html, NULL);
1360     }
1361 }
1362
1363 static void
1364 cmd_modkey() {
1365     int i;
1366     char *buf;
1367
1368     buf = g_utf8_strup(uzbl.behave.modkey, -1);
1369     uzbl.behave.modmask = 0;
1370
1371     if(uzbl.behave.modkey) 
1372         g_free(uzbl.behave.modkey);
1373     uzbl.behave.modkey = buf;
1374
1375     for (i = 0; modkeys[i].key != NULL; i++) {
1376         if (g_strrstr(buf, modkeys[i].key))
1377             uzbl.behave.modmask |= modkeys[i].mask;
1378     }
1379 }
1380
1381 static void
1382 cmd_useragent() {
1383     if (*uzbl.net.useragent == ' ') {
1384         g_free (uzbl.net.useragent);
1385         uzbl.net.useragent = NULL;
1386     } else {
1387         gchar *ua = expand_template(uzbl.net.useragent, FALSE);
1388         if (ua)
1389             g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1390         g_free(uzbl.net.useragent);
1391         uzbl.net.useragent = ua;
1392     }
1393 }
1394
1395 static void
1396 move_statusbar() {
1397     gtk_widget_ref(uzbl.gui.scrolled_win);
1398     gtk_widget_ref(uzbl.gui.mainbar);
1399     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1400     gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1401
1402     if(uzbl.behave.status_top) {
1403         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1404         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1405     }
1406     else {
1407         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1408         gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1409     }
1410     gtk_widget_unref(uzbl.gui.scrolled_win);
1411     gtk_widget_unref(uzbl.gui.mainbar);
1412     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1413     return;
1414 }
1415
1416 static gboolean
1417 set_var_value(gchar *name, gchar *val) {
1418     uzbl_cmdprop *c = NULL;
1419     char *endp = NULL;
1420
1421     if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1422         /* check for the variable type */
1423         if (c->type == TYPE_STR) {
1424             g_free(*c->ptr);
1425             *c->ptr = g_strdup(val);
1426         } else if(c->type == TYPE_INT) {
1427             int *ip = (int *)c->ptr;
1428             *ip = (int)strtoul(val, &endp, 10);
1429         }
1430
1431         /* invoke a command specific function */
1432         if(c->func) c->func();
1433     }
1434     return TRUE;
1435 }
1436
1437 static void
1438 runcmd(WebKitWebView* page, GArray *argv) {
1439     (void) page;
1440     parse_cmd_line(argv_idx(argv, 0));
1441 }
1442
1443 static void
1444 render_html() {
1445     Behaviour *b = &uzbl.behave;
1446
1447     if(b->html_buffer->str) {
1448         webkit_web_view_load_html_string (uzbl.gui.web_view,
1449                 b->html_buffer->str, b->base_url);
1450         g_string_free(b->html_buffer, TRUE);
1451         b->html_buffer = g_string_new("");
1452     }
1453 }
1454
1455 enum {M_CMD, M_HTML};
1456 static void
1457 parse_cmd_line(const char *ctl_line) {
1458     gchar **tokens = NULL;
1459     Behaviour *b = &uzbl.behave;
1460
1461     if(b->mode == M_HTML) {
1462
1463         if(!strncmp(b->html_endmarker, ctl_line, strlen(b->html_endmarker))) {
1464             set_timeout(0);
1465             set_var_value("mode", "0");
1466             render_html();
1467             return;
1468         }
1469         else {
1470             /* set an alarm to kill us after the timeout */
1471             set_timeout(b->html_timeout);
1472             g_string_append(b->html_buffer, ctl_line);
1473         }
1474     }
1475     else {
1476         /* SET command */
1477         if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1478             tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1479             if(tokens[0][0] == 0) {
1480                 gchar* value = parseenv(g_strdup(tokens[2]));
1481                 set_var_value(tokens[1], value);
1482                 g_free(value);
1483             }
1484             else
1485                 printf("Error in command: %s\n", tokens[0]);
1486         }
1487         /* GET command */
1488         else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1489             tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1490             if(tokens[0][0] == 0) {
1491                 get_var_value(tokens[1]);
1492             }
1493             else
1494                 printf("Error in command: %s\n", tokens[0]);
1495         }
1496         /* BIND command */
1497         else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1498             tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1499             if(tokens[0][0] == 0) {
1500                 gchar* value = parseenv(g_strdup(tokens[2]));
1501                 add_binding(tokens[1], value);
1502                 g_free(value);
1503             }
1504             else
1505                 printf("Error in command: %s\n", tokens[0]);
1506         }
1507         /* ACT command */
1508         else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1509             tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1510             if(tokens[0][0] == 0) {
1511                 parse_command(tokens[1], tokens[2]);
1512             }
1513             else
1514                 printf("Error in command: %s\n", tokens[0]);
1515         }
1516         /* KEYCMD command */
1517         else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1518             tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1519             if(tokens[0][0] == 0) {
1520                 /* should incremental commands want each individual "keystroke"
1521                    sent in a loop or the whole string in one go like now? */
1522                 g_string_assign(uzbl.state.keycmd, tokens[1]);
1523                 run_keycmd(FALSE);
1524                 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1525                     run_keycmd(TRUE);
1526                 update_title();
1527             }
1528         }
1529         /* Comments */
1530         else if(   (ctl_line[0] == '#')
1531                 || (ctl_line[0] == ' ')
1532                 || (ctl_line[0] == '\n'))
1533             ; /* ignore these lines */
1534         else
1535             printf("Command not understood (%s)\n", ctl_line);
1536
1537         if(tokens)
1538             g_strfreev(tokens);
1539     }
1540
1541     return;
1542 }
1543
1544 static gchar*
1545 build_stream_name(int type, const gchar* dir) {
1546     char *xwin_str;
1547     State *s = &uzbl.state;
1548     gchar *str;
1549
1550     xwin_str = itos((int)uzbl.xwin);
1551     if (type == FIFO) {
1552         str = g_strdup_printf
1553             ("%s/uzbl_fifo_%s", dir,
1554              s->instance_name ? s->instance_name : xwin_str);
1555     } else if (type == SOCKET) {
1556         str = g_strdup_printf
1557             ("%s/uzbl_socket_%s", dir,
1558              s->instance_name ? s->instance_name : xwin_str );
1559     }
1560     g_free(xwin_str);
1561     return str;
1562 }
1563
1564 static gboolean
1565 control_fifo(GIOChannel *gio, GIOCondition condition) {
1566     if (uzbl.state.verbose)
1567         printf("triggered\n");
1568     gchar *ctl_line;
1569     GIOStatus ret;
1570     GError *err = NULL;
1571
1572     if (condition & G_IO_HUP)
1573         g_error ("Fifo: Read end of pipe died!\n");
1574
1575     if(!gio)
1576        g_error ("Fifo: GIOChannel broke\n");
1577
1578     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1579     if (ret == G_IO_STATUS_ERROR) {
1580         g_error ("Fifo: Error reading: %s\n", err->message);
1581         g_error_free (err);
1582     }
1583
1584     parse_cmd_line(ctl_line);
1585     g_free(ctl_line);
1586
1587     return TRUE;
1588 }
1589
1590 static gchar*
1591 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1592     if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1593         if (unlink(uzbl.comm.fifo_path) == -1)
1594             g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1595         g_free(uzbl.comm.fifo_path);
1596         uzbl.comm.fifo_path = NULL;
1597     }
1598
1599     if (*dir == ' ') { /* space unsets the variable */
1600         g_free (dir);
1601         return NULL;
1602     }
1603
1604     GIOChannel *chan = NULL;
1605     GError *error = NULL;
1606     gchar *path = build_stream_name(FIFO, dir);
1607
1608     if (!file_exists(path)) {
1609         if (mkfifo (path, 0666) == 0) {
1610             // 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.
1611             chan = g_io_channel_new_file(path, "r+", &error);
1612             if (chan) {
1613                 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1614                     if (uzbl.state.verbose)
1615                         printf ("init_fifo: created successfully as %s\n", path);
1616                     uzbl.comm.fifo_path = path;
1617                     return dir;
1618                 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1619             } else g_warning ("init_fifo: can't open: %s\n", error->message);
1620         } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1621     } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1622
1623     /* if we got this far, there was an error; cleanup */
1624     if (error) g_error_free (error);
1625     g_free(dir);
1626     g_free(path);
1627     return NULL;
1628 }
1629
1630 static gboolean
1631 control_stdin(GIOChannel *gio, GIOCondition condition) {
1632     (void) condition;
1633     gchar *ctl_line = NULL;
1634     GIOStatus ret;
1635
1636     ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1637     if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1638         return FALSE;
1639
1640     parse_cmd_line(ctl_line);
1641     g_free(ctl_line);
1642
1643     return TRUE;
1644 }
1645
1646 static void
1647 create_stdin () {
1648     GIOChannel *chan = NULL;
1649     GError *error = NULL;
1650
1651     chan = g_io_channel_unix_new(fileno(stdin));
1652     if (chan) {
1653         if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1654             g_error ("Stdin: could not add watch\n");
1655         } else {
1656             if (uzbl.state.verbose)
1657                 printf ("Stdin: watch added successfully\n");
1658         }
1659     } else {
1660         g_error ("Stdin: Error while opening: %s\n", error->message);
1661     }
1662     if (error) g_error_free (error);
1663 }
1664
1665 static gboolean
1666 control_socket(GIOChannel *chan) {
1667     struct sockaddr_un remote;
1668     char buffer[512], *ctl_line;
1669     char temp[128];
1670     int sock, clientsock, n, done;
1671     unsigned int t;
1672
1673     sock = g_io_channel_unix_get_fd(chan);
1674
1675     memset (buffer, 0, sizeof (buffer));
1676
1677     t          = sizeof (remote);
1678     clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1679
1680     done = 0;
1681     do {
1682         memset (temp, 0, sizeof (temp));
1683         n = recv (clientsock, temp, 128, 0);
1684         if (n == 0) {
1685             buffer[strlen (buffer)] = '\0';
1686             done = 1;
1687         }
1688         if (!done)
1689             strcat (buffer, temp);
1690     } while (!done);
1691
1692     if (strcmp (buffer, "\n") < 0) {
1693         buffer[strlen (buffer) - 1] = '\0';
1694     } else {
1695         buffer[strlen (buffer)] = '\0';
1696     }
1697     close (clientsock);
1698     ctl_line = g_strdup(buffer);
1699     parse_cmd_line (ctl_line);
1700
1701 /*
1702    TODO: we should be able to do it with this.  but glib errors out with "Invalid argument"
1703     GError *error = NULL;
1704     gsize len;
1705     GIOStatus ret;
1706     ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1707     if (ret == G_IO_STATUS_ERROR)
1708         g_error ("Error reading: %s\n", error->message);
1709
1710     printf("Got line %s (%u bytes) \n",ctl_line, len);
1711     if(ctl_line) {
1712        parse_line(ctl_line);
1713 */
1714
1715     g_free(ctl_line);
1716     return TRUE;
1717 }
1718
1719 static gchar*
1720 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1721     if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1722         if (unlink(uzbl.comm.socket_path) == -1)
1723             g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1724         g_free(uzbl.comm.socket_path);
1725         uzbl.comm.socket_path = NULL;
1726     }
1727
1728     if (*dir == ' ') {
1729         g_free(dir);
1730         return NULL;
1731     }
1732
1733     GIOChannel *chan = NULL;
1734     int sock, len;
1735     struct sockaddr_un local;
1736     gchar *path = build_stream_name(SOCKET, dir);
1737
1738     sock = socket (AF_UNIX, SOCK_STREAM, 0);
1739
1740     local.sun_family = AF_UNIX;
1741     strcpy (local.sun_path, path);
1742     unlink (local.sun_path);
1743
1744     len = strlen (local.sun_path) + sizeof (local.sun_family);
1745     if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1746         if (uzbl.state.verbose)
1747             printf ("init_socket: opened in %s\n", path);
1748         listen (sock, 5);
1749
1750         if( (chan = g_io_channel_unix_new(sock)) ) {
1751             g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1752             uzbl.comm.socket_path = path;
1753             return dir;
1754         }
1755     } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1756
1757     /* if we got this far, there was an error; cleanup */
1758     g_free(path);
1759     g_free(dir);
1760     return NULL;
1761 }
1762
1763 /*
1764  NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1765  it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1766 */
1767 // this function may be called very early when the templates are not set (yet), hence the checks
1768 static void
1769 update_title (void) {
1770     Behaviour *b = &uzbl.behave;
1771     gchar *parsed;
1772
1773     if (b->show_status) {
1774         if (b->title_format_short) {
1775             parsed = expand_template(b->title_format_short, FALSE);
1776             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1777             g_free(parsed);
1778         }
1779         if (b->status_format) {
1780             parsed = expand_template(b->status_format, TRUE);
1781             gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1782             g_free(parsed);
1783         }
1784         if (b->status_background) {
1785             GdkColor color;
1786             gdk_color_parse (b->status_background, &color);
1787             //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)
1788             gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1789         }
1790     } else {
1791         if (b->title_format_long) {
1792             parsed = expand_template(b->title_format_long, FALSE);
1793             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1794             g_free(parsed);
1795         }
1796     }
1797 }
1798
1799 static gboolean
1800 key_press_cb (GtkWidget* window, GdkEventKey* event)
1801 {
1802     //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1803
1804     (void) window;
1805
1806     if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1807         || 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)
1808         return FALSE;
1809
1810     /* turn off insert mode (if always_insert_mode is not used) */
1811     if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1812         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1813         update_title();
1814         return TRUE;
1815     }
1816
1817     if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1818         return FALSE;
1819
1820     if (event->keyval == GDK_Escape) {
1821         g_string_truncate(uzbl.state.keycmd, 0);
1822         update_title();
1823         dehilight(uzbl.gui.web_view, NULL);
1824         return TRUE;
1825     }
1826
1827     //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1828     if (event->keyval == GDK_Insert) {
1829         gchar * str;
1830         if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1831             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1832         } else {
1833             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1834         }
1835         if (str) {
1836             g_string_append (uzbl.state.keycmd, str);
1837             update_title ();
1838             g_free (str);
1839         }
1840         return TRUE;
1841     }
1842
1843     if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1844         g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1845         update_title();
1846     }
1847
1848     gboolean key_ret = FALSE;
1849     if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1850         key_ret = TRUE;
1851     if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1852
1853     run_keycmd(key_ret);
1854     update_title();
1855     if (key_ret) return (!uzbl.behave.insert_mode);
1856     return TRUE;
1857 }
1858
1859 static void
1860 run_keycmd(const gboolean key_ret) {
1861     /* run the keycmd immediately if it isn't incremental and doesn't take args */
1862     Action *act;
1863     if ((act = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1864         g_string_truncate(uzbl.state.keycmd, 0);
1865         parse_command(act->name, act->param);
1866         return;
1867     }
1868
1869     /* try if it's an incremental keycmd or one that takes args, and run it */
1870     GString* short_keys = g_string_new ("");
1871     GString* short_keys_inc = g_string_new ("");
1872     guint i;
1873     for (i=0; i<(uzbl.state.keycmd->len); i++) {
1874         g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1875         g_string_assign(short_keys_inc, short_keys->str);
1876         g_string_append_c(short_keys, '_');
1877         g_string_append_c(short_keys_inc, '*');
1878
1879         if (key_ret && (act = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1880             /* run normal cmds only if return was pressed */
1881             exec_paramcmd(act, i);
1882             g_string_truncate(uzbl.state.keycmd, 0);
1883             break;
1884         } else if ((act = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1885             if (key_ret)  /* just quit the incremental command on return */
1886                 g_string_truncate(uzbl.state.keycmd, 0);
1887             else exec_paramcmd(act, i); /* otherwise execute the incremental */
1888             break;
1889         }
1890         
1891         g_string_truncate(short_keys, short_keys->len - 1);
1892     }
1893     g_string_free (short_keys, TRUE);
1894     g_string_free (short_keys_inc, TRUE);
1895 }
1896
1897 static void
1898 exec_paramcmd(const Action *act, const guint i) {
1899     GString *parampart = g_string_new (uzbl.state.keycmd->str);
1900     GString *actionname = g_string_new ("");
1901     GString *actionparam = g_string_new ("");
1902     g_string_erase (parampart, 0, i+1);
1903     if (act->name)
1904         g_string_printf (actionname, act->name, parampart->str);
1905     if (act->param)
1906         g_string_printf (actionparam, act->param, parampart->str);
1907     parse_command(actionname->str, actionparam->str);
1908     g_string_free(actionname, TRUE);
1909     g_string_free(actionparam, TRUE);
1910     g_string_free(parampart, TRUE);
1911 }
1912
1913
1914 static GtkWidget*
1915 create_browser () {
1916     GUI *g = &uzbl.gui;
1917
1918     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1919     //main_window_ref = g_object_ref(scrolled_window);
1920     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
1921
1922     g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1923     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1924
1925     g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1926     g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1927     g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1928     g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1929     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1930     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1931     g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1932     g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1933     g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1934     g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1935
1936     return scrolled_window;
1937 }
1938
1939 static GtkWidget*
1940 create_mainbar () {
1941     GUI *g = &uzbl.gui;
1942
1943     g->mainbar = gtk_hbox_new (FALSE, 0);
1944
1945     /* keep a reference to the bar so we can re-pack it at runtime*/
1946     //sbar_ref = g_object_ref(g->mainbar);
1947
1948     g->mainbar_label = gtk_label_new ("");
1949     gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1950     gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1951     gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1952     gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1953     gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1954     return g->mainbar;
1955 }
1956
1957 static
1958 GtkWidget* create_window () {
1959     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1960     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1961     gtk_widget_set_name (window, "Uzbl browser");
1962     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1963     g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);
1964
1965     return window;
1966 }
1967
1968 static void
1969 run_handler (const gchar *act, const gchar *args) {
1970     char **parts = g_strsplit(act, " ", 2);
1971     if (!parts) return;
1972     else if ((g_strcmp0(parts[0], "spawn") == 0)
1973              || (g_strcmp0(parts[0], "sh") == 0)
1974              || (g_strcmp0(parts[0], "sync_spawn") == 0)
1975              || (g_strcmp0(parts[0], "sync_sh") == 0)) {
1976         guint i;
1977         GString *a = g_string_new ("");
1978         char **spawnparts;
1979         spawnparts = split_quoted(parts[1], FALSE);
1980         g_string_append_printf(a, "%s", spawnparts[0]);
1981         if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1982         
1983         for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1984             g_string_append_printf(a, " %s", spawnparts[i]);
1985         parse_command(parts[0], a->str);
1986         g_string_free (a, TRUE);
1987         g_strfreev (spawnparts);
1988     } else
1989         parse_command(parts[0], parts[1]);
1990     g_strfreev (parts);
1991 }
1992
1993 static void
1994 add_binding (const gchar *key, const gchar *act) {
1995     char **parts = g_strsplit(act, " ", 2);
1996     Action *action;
1997
1998     if (!parts)
1999         return;
2000
2001     //Debug:
2002     if (uzbl.state.verbose)
2003         printf ("Binding %-10s : %s\n", key, act);
2004     action = new_action(parts[0], parts[1]);
2005
2006     g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
2007     g_strfreev(parts);
2008 }
2009
2010 static gchar*
2011 get_xdg_var (XDG_Var xdg) {
2012     const gchar* actual_value = getenv (xdg.environmental);
2013     const gchar* home         = getenv ("HOME");
2014
2015     gchar* return_value = str_replace ("~", home, actual_value);
2016
2017     if (! actual_value || strcmp (actual_value, "") == 0) {
2018         if (xdg.default_value) {
2019             return_value = str_replace ("~", home, xdg.default_value);
2020         } else {
2021             return_value = NULL;
2022         }
2023     }
2024     return return_value;
2025 }
2026
2027 static gchar*
2028 find_xdg_file (int xdg_type, char* filename) {
2029     /* xdg_type = 0 => config
2030        xdg_type = 1 => data
2031        xdg_type = 2 => cache*/
2032
2033     gchar* xdgv = get_xdg_var (XDG[xdg_type]);
2034     gchar* temporary_file = g_strconcat (xdgv, filename, NULL);
2035     g_free (xdgv);
2036
2037     gchar* temporary_string;
2038     char*  saveptr;
2039     char*  buf;
2040
2041     if (! file_exists (temporary_file) && xdg_type != 2) {
2042         buf = get_xdg_var (XDG[3 + xdg_type]);
2043         temporary_string = (char *) strtok_r (buf, ":", &saveptr);
2044         g_free(buf);
2045
2046         while ((temporary_string = (char * ) strtok_r (NULL, ":", &saveptr)) && ! file_exists (temporary_file)) {
2047             g_free (temporary_file);
2048             temporary_file = g_strconcat (temporary_string, filename, NULL);
2049         }
2050     }
2051     
2052     //g_free (temporary_string); - segfaults.
2053
2054     if (file_exists (temporary_file)) {
2055         return temporary_file;
2056     } else {
2057         return NULL;
2058     }
2059 }
2060 static void
2061 settings_init () {
2062     State *s = &uzbl.state;
2063     Network *n = &uzbl.net;
2064     int i;
2065     for (i = 0; default_config[i].command != NULL; i++) {
2066         parse_cmd_line(default_config[i].command);
2067     }
2068
2069     if (!s->config_file) {
2070         s->config_file = find_xdg_file (0, "/uzbl/config");
2071     }
2072
2073     if (s->config_file) {
2074         GArray* lines = read_file_by_line (s->config_file);
2075         int i = 0;
2076         gchar* line;
2077
2078         while ((line = g_array_index(lines, gchar*, i))) {
2079             parse_cmd_line (line);
2080             i ++;
2081             g_free (line);
2082         }
2083         g_array_free (lines, TRUE);
2084     } else {
2085         if (uzbl.state.verbose)
2086             printf ("No configuration file loaded.\n");
2087     }
2088
2089     g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
2090 }
2091
2092 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
2093     (void) session;
2094     (void) user_data;
2095     if (!uzbl.behave.cookie_handler) return;
2096
2097     soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
2098     GString *s = g_string_new ("");
2099     SoupURI * soup_uri = soup_message_get_uri(msg);
2100     g_string_printf(s, "GET '%s' '%s'", soup_uri->host, soup_uri->path);
2101     run_handler(uzbl.behave.cookie_handler, s->str);
2102
2103     if(uzbl.comm.sync_stdout)
2104         soup_message_headers_replace (msg->request_headers, "Cookie", uzbl.comm.sync_stdout);
2105     //printf("stdout: %s\n", uzbl.comm.sync_stdout);   // debugging
2106     if (uzbl.comm.sync_stdout) uzbl.comm.sync_stdout = strfree(uzbl.comm.sync_stdout);
2107         
2108     g_string_free(s, TRUE);
2109 }
2110
2111 static void
2112 save_cookies (SoupMessage *msg, gpointer user_data){
2113     (void) user_data;
2114     GSList *ck;
2115     char *cookie;
2116     for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
2117         cookie = soup_cookie_to_set_cookie_header(ck->data);
2118         SoupURI * soup_uri = soup_message_get_uri(msg);
2119         GString *s = g_string_new ("");
2120         g_string_printf(s, "PUT '%s' '%s' '%s'", soup_uri->host, soup_uri->path, cookie);
2121         run_handler(uzbl.behave.cookie_handler, s->str);
2122         g_free (cookie);
2123         g_string_free(s, TRUE);
2124     }
2125     g_slist_free(ck);
2126 }
2127
2128 /* --- WEBINSPECTOR --- */
2129 static void
2130 hide_window_cb(GtkWidget *widget, gpointer data) {
2131     (void) data;
2132
2133     gtk_widget_hide(widget);
2134 }
2135
2136 static WebKitWebView*
2137 create_inspector_cb (WebKitWebInspector* web_inspector, WebKitWebView* page, gpointer data){
2138     (void) data;
2139     (void) page;
2140     (void) web_inspector;
2141     GtkWidget* scrolled_window;
2142     GtkWidget* new_web_view;
2143     GUI *g = &uzbl.gui;
2144
2145     g->inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2146     g_signal_connect(G_OBJECT(g->inspector_window), "delete-event",
2147             G_CALLBACK(hide_window_cb), NULL);
2148
2149     gtk_window_set_title(GTK_WINDOW(g->inspector_window), "Uzbl WebInspector");
2150     gtk_window_set_default_size(GTK_WINDOW(g->inspector_window), 400, 300);
2151     gtk_widget_show(g->inspector_window);
2152
2153     scrolled_window = gtk_scrolled_window_new(NULL, NULL);
2154     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
2155             GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2156     gtk_container_add(GTK_CONTAINER(g->inspector_window), scrolled_window);
2157     gtk_widget_show(scrolled_window);
2158
2159     new_web_view = webkit_web_view_new();
2160     gtk_container_add(GTK_CONTAINER(scrolled_window), new_web_view);
2161
2162     return WEBKIT_WEB_VIEW(new_web_view);
2163 }
2164
2165 static gboolean
2166 inspector_show_window_cb (WebKitWebInspector* inspector){
2167     (void) inspector;
2168     gtk_widget_show(uzbl.gui.inspector_window);
2169     return TRUE;
2170 }
2171
2172 /* TODO: Add variables and code to make use of these functions */
2173 static gboolean
2174 inspector_close_window_cb (WebKitWebInspector* inspector){
2175     (void) inspector;
2176     return TRUE;
2177 }
2178
2179 static gboolean
2180 inspector_attach_window_cb (WebKitWebInspector* inspector){
2181     (void) inspector;
2182     return FALSE;
2183 }
2184
2185 static gboolean
2186 inspector_dettach_window_cb (WebKitWebInspector* inspector){
2187     (void) inspector;
2188     return FALSE;
2189 }
2190
2191 static gboolean
2192 inspector_uri_changed_cb (WebKitWebInspector* inspector){
2193     (void) inspector;
2194     return FALSE;
2195 }
2196
2197 static gboolean
2198 inspector_inspector_destroyed_cb (WebKitWebInspector* inspector){
2199     (void) inspector;
2200     return FALSE;
2201 }
2202
2203 static void
2204 set_up_inspector() {
2205     GUI *g = &uzbl.gui;
2206     WebKitWebSettings *settings = webkit_web_settings_new();
2207     g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL);
2208     webkit_web_view_set_settings(WEBKIT_WEB_VIEW(uzbl.gui.web_view), settings);
2209
2210
2211     uzbl.gui.inspector = webkit_web_view_get_inspector(uzbl.gui.web_view);
2212     g_signal_connect (G_OBJECT (g->inspector), "inspect-web-view", G_CALLBACK (create_inspector_cb), NULL);
2213     g_signal_connect (G_OBJECT (g->inspector), "show-window", G_CALLBACK (inspector_show_window_cb), NULL);
2214     g_signal_connect (G_OBJECT (g->inspector), "close-window", G_CALLBACK (inspector_close_window_cb), NULL);
2215     g_signal_connect (G_OBJECT (g->inspector), "attach-window", G_CALLBACK (inspector_attach_window_cb), NULL);
2216     g_signal_connect (G_OBJECT (g->inspector), "dettach-window", G_CALLBACK (inspector_dettach_window_cb), NULL);
2217     g_signal_connect (G_OBJECT (g->inspector), "destroy", G_CALLBACK (inspector_inspector_destroyed_cb), NULL);
2218
2219     g_signal_connect (G_OBJECT (g->inspector), "notify::inspected-uri", G_CALLBACK (inspector_uri_changed_cb), NULL);
2220 }
2221
2222
2223
2224 /** -- MAIN -- **/
2225 int
2226 main (int argc, char* argv[]) {
2227     gtk_init (&argc, &argv);
2228     if (!g_thread_supported ())
2229         g_thread_init (NULL);
2230     uzbl.state.executable_path = g_strdup(argv[0]);
2231     uzbl.state.selected_url = NULL;
2232     uzbl.state.searchtx = NULL;
2233
2234     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
2235     g_option_context_add_main_entries (context, entries, NULL);
2236     g_option_context_add_group (context, gtk_get_option_group (TRUE));
2237     g_option_context_parse (context, &argc, &argv, NULL);
2238     g_option_context_free(context);
2239     /* initialize hash table */
2240     uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
2241
2242     uzbl.net.soup_session = webkit_get_default_session();
2243     uzbl.state.keycmd = g_string_new("");
2244
2245     if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
2246         fprintf(stderr, "uzbl: error hooking SIGTERM\n");
2247     if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
2248         fprintf(stderr, "uzbl: error hooking SIGINT\n");
2249     if(setup_signal(SIGALRM, catch_alrm) == SIG_ERR)
2250         fprintf(stderr, "uzbl: error hooking SIGALARM\n");
2251
2252
2253     if(uname(&uzbl.state.unameinfo) == -1)
2254         g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");
2255
2256     uzbl.gui.sbar.progress_s = g_strdup("=");
2257     uzbl.gui.sbar.progress_u = g_strdup("ยท");
2258     uzbl.gui.sbar.progress_w = 10;
2259
2260     /* HTML mode defaults*/
2261     uzbl.behave.html_buffer = g_string_new("");
2262     uzbl.behave.html_endmarker = g_strdup(".");
2263     uzbl.behave.html_timeout = 60;
2264     uzbl.behave.base_url = g_strdup("http://invalid");
2265
2266     setup_regex();
2267     setup_scanner();
2268     commands_hash ();
2269     make_var_to_name_hash();
2270
2271     uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
2272
2273     uzbl.gui.scrolled_win = create_browser();
2274     create_mainbar();
2275
2276     /* initial packing */
2277     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
2278     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
2279
2280     uzbl.gui.main_window = create_window ();
2281     gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
2282
2283
2284     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
2285     gtk_widget_show_all (uzbl.gui.main_window);
2286     uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
2287
2288     if (uzbl.state.verbose) {
2289         printf("Uzbl start location: %s\n", argv[0]);
2290         printf("window_id %i\n",(int) uzbl.xwin);
2291         printf("pid %i\n", getpid ());
2292         printf("name: %s\n", uzbl.state.instance_name);
2293     }
2294
2295     uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
2296     uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
2297     uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
2298     uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
2299     gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
2300
2301     settings_init ();
2302
2303     if (!uzbl.behave.show_status)
2304         gtk_widget_hide(uzbl.gui.mainbar);
2305     else
2306         update_title();
2307
2308     /* WebInspector */
2309     set_up_inspector();
2310
2311     create_stdin();
2312
2313     if(uzbl.state.uri) {
2314         GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
2315         g_array_append_val(a, uzbl.state.uri);
2316         load_uri (uzbl.gui.web_view, a);
2317         g_array_free (a, TRUE);
2318     }
2319
2320     gtk_main ();
2321     clean_up();
2322
2323     return EXIT_SUCCESS;
2324 }
2325
2326 /* vi: set et ts=4: */