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