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