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