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