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