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