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