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