2c3f8b3ffa6c4298e29c7940274ea177a23ed380
[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     { "toggle_insert_mode", toggle_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 void
460 toggle_insert_mode(WebKitWebView *page, const gchar *param) {
461     (void)page;
462     (void)param;
463
464     uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
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     gchar *ctl_line = NULL;
1168     gsize ctl_line_len = 0;
1169     GIOStatus ret;
1170
1171     if (condition & G_IO_HUP) {
1172         ret = g_io_channel_shutdown (gio, FALSE, NULL);
1173         return FALSE;
1174     }
1175
1176     ret = g_io_channel_read_line(gio, &ctl_line, &ctl_line_len, NULL, NULL);
1177     if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1178         return FALSE;
1179
1180     parse_cmd_line(ctl_line);
1181     g_free(ctl_line);
1182
1183     return TRUE;
1184 }
1185
1186 static void
1187 create_stdin () {
1188     GIOChannel *chan = NULL;
1189     GError *error = NULL;
1190
1191     chan = g_io_channel_unix_new(fileno(stdin));
1192     if (chan) {
1193         if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1194             g_error ("Stdin: could not add watch\n");
1195         } else {
1196             if (uzbl.state.verbose)
1197                 printf ("Stdin: watch added successfully\n");
1198         }
1199     } else {
1200         g_error ("Stdin: Error while opening: %s\n", error->message);
1201     }
1202     if (error) g_error_free (error);
1203 }
1204
1205 static gboolean
1206 control_socket(GIOChannel *chan) {
1207     struct sockaddr_un remote;
1208     char buffer[512], *ctl_line;
1209     char temp[128];
1210     int sock, clientsock, n, done;
1211     unsigned int t;
1212
1213     sock = g_io_channel_unix_get_fd(chan);
1214
1215     memset (buffer, 0, sizeof (buffer));
1216
1217     t          = sizeof (remote);
1218     clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1219
1220     done = 0;
1221     do {
1222         memset (temp, 0, sizeof (temp));
1223         n = recv (clientsock, temp, 128, 0);
1224         if (n == 0) {
1225             buffer[strlen (buffer)] = '\0';
1226             done = 1;
1227         }
1228         if (!done)
1229             strcat (buffer, temp);
1230     } while (!done);
1231
1232     if (strcmp (buffer, "\n") < 0) {
1233         buffer[strlen (buffer) - 1] = '\0';
1234     } else {
1235         buffer[strlen (buffer)] = '\0';
1236     }
1237     close (clientsock);
1238     ctl_line = g_strdup(buffer);
1239     parse_cmd_line (ctl_line);
1240
1241 /*
1242    TODO: we should be able to do it with this.  but glib errors out with "Invalid argument"
1243     GError *error = NULL;
1244     gsize len;
1245     GIOStatus ret;
1246     ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1247     if (ret == G_IO_STATUS_ERROR)
1248         g_error ("Error reading: %s\n", error->message);
1249
1250     printf("Got line %s (%u bytes) \n",ctl_line, len);
1251     if(ctl_line) {
1252        parse_line(ctl_line);
1253 */
1254
1255     g_free(ctl_line);
1256     return TRUE;
1257 }
1258
1259 static gchar*
1260 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1261     if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1262         if (unlink(uzbl.comm.socket_path) == -1)
1263             g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1264         g_free(uzbl.comm.socket_path);
1265         uzbl.comm.socket_path = NULL;
1266     }
1267
1268     if (*dir == ' ') {
1269         g_free(dir);
1270         return NULL;
1271     }
1272
1273     GIOChannel *chan = NULL;
1274     int sock, len;
1275     struct sockaddr_un local;
1276     gchar *path = build_stream_name(SOCKET, dir);
1277
1278     sock = socket (AF_UNIX, SOCK_STREAM, 0);
1279
1280     local.sun_family = AF_UNIX;
1281     strcpy (local.sun_path, path);
1282     unlink (local.sun_path);
1283
1284     len = strlen (local.sun_path) + sizeof (local.sun_family);
1285     if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1286         if (uzbl.state.verbose)
1287             printf ("init_socket: opened in %s\n", path);
1288         listen (sock, 5);
1289
1290         if( (chan = g_io_channel_unix_new(sock)) ) {
1291             g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1292             uzbl.comm.socket_path = path;
1293             return dir;
1294         }
1295     } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1296
1297     /* if we got this far, there was an error; cleanup */
1298     g_free(path);
1299     g_free(dir);
1300     return NULL;
1301 }
1302
1303 /*
1304  NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1305  it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1306 */
1307 // this function may be called very early when the templates are not set (yet), hence the checks
1308 static void
1309 update_title (void) {
1310     Behaviour *b = &uzbl.behave;
1311     gchar *parsed;
1312
1313     if (b->show_status) {
1314         if (b->title_format_short) {
1315             parsed = expand_template(b->title_format_short);
1316             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1317             g_free(parsed);
1318         }
1319         if (b->status_format) {
1320             parsed = expand_template(b->status_format);
1321             gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1322             g_free(parsed);
1323         }
1324         if (b->status_background) {
1325             GdkColor color;
1326             gdk_color_parse (b->status_background, &color);
1327             //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)
1328             gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1329         }
1330     } else {
1331         if (b->title_format_long) {
1332             parsed = expand_template(b->title_format_long);
1333             gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1334             g_free(parsed);
1335         }
1336     }
1337 }
1338
1339 static gboolean
1340 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1341 {
1342     //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1343
1344     (void) page;
1345
1346     if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1347         || 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)
1348         return FALSE;
1349
1350     /* turn off insert mode (if always_insert_mode is not used) */
1351     if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1352         uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1353         update_title();
1354         return TRUE;
1355     }
1356
1357     if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1358         return FALSE;
1359
1360     if (event->keyval == GDK_Escape) {
1361         g_string_truncate(uzbl.state.keycmd, 0);
1362         update_title();
1363         return TRUE;
1364     }
1365
1366     //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1367     if (event->keyval == GDK_Insert) {
1368         gchar * str;
1369         if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1370             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1371         } else {
1372             str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1373         }
1374         if (str) {
1375             g_string_append (uzbl.state.keycmd, str);
1376             update_title ();
1377             free (str);
1378         }
1379         return TRUE;
1380     }
1381
1382     if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1383         g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1384         update_title();
1385     }
1386
1387     gboolean key_ret = FALSE;
1388     if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1389         key_ret = TRUE;
1390     if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1391
1392     run_keycmd(key_ret);
1393     update_title();
1394     if (key_ret) return (!uzbl.behave.insert_mode);
1395     return TRUE;
1396 }
1397
1398 static void
1399 run_keycmd(const gboolean key_ret) {
1400     /* run the keycmd immediately if it isn't incremental and doesn't take args */
1401     Action *action;
1402     if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1403         g_string_truncate(uzbl.state.keycmd, 0);
1404         parse_command(action->name, action->param);
1405         return;
1406     }
1407
1408     /* try if it's an incremental keycmd or one that takes args, and run it */
1409     GString* short_keys = g_string_new ("");
1410     GString* short_keys_inc = g_string_new ("");
1411     unsigned int i;
1412     for (i=0; i<(uzbl.state.keycmd->len); i++) {
1413         g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1414         g_string_assign(short_keys_inc, short_keys->str);
1415         g_string_append_c(short_keys, '_');
1416         g_string_append_c(short_keys_inc, '*');
1417
1418         gboolean exec_now = FALSE;
1419         if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1420             if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1421         } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1422             if (key_ret) { /* just quit the incremental command on return */
1423                 g_string_truncate(uzbl.state.keycmd, 0);
1424                 break;
1425             } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1426         }
1427
1428         if (exec_now) {
1429             GString* parampart = g_string_new (uzbl.state.keycmd->str);
1430             GString* actionname = g_string_new ("");
1431             GString* actionparam = g_string_new ("");
1432             g_string_erase (parampart, 0, i+1);
1433             if (action->name)
1434                 g_string_printf (actionname, action->name, parampart->str);
1435             if (action->param)
1436                 g_string_printf (actionparam, action->param, parampart->str);
1437             parse_command(actionname->str, actionparam->str);
1438             g_string_free (actionname, TRUE);
1439             g_string_free (actionparam, TRUE);
1440             g_string_free (parampart, TRUE);
1441             if (key_ret)
1442                 g_string_truncate(uzbl.state.keycmd, 0);
1443             break;
1444         }
1445
1446         g_string_truncate(short_keys, short_keys->len - 1);
1447     }
1448     g_string_free (short_keys, TRUE);
1449     g_string_free (short_keys_inc, TRUE);
1450 }
1451
1452 static GtkWidget*
1453 create_browser () {
1454     GUI *g = &uzbl.gui;
1455
1456     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1457     //main_window_ref = g_object_ref(scrolled_window);
1458     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
1459
1460     g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1461     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1462
1463     g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1464     g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1465     g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1466     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1467     g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1468     g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1469     g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1470     g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1471     g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1472     g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1473
1474     return scrolled_window;
1475 }
1476
1477 static GtkWidget*
1478 create_mainbar () {
1479     GUI *g = &uzbl.gui;
1480
1481     g->mainbar = gtk_hbox_new (FALSE, 0);
1482
1483     /* keep a reference to the bar so we can re-pack it at runtime*/
1484     //sbar_ref = g_object_ref(g->mainbar);
1485
1486     g->mainbar_label = gtk_label_new ("");
1487     gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1488     gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1489     gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1490     gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1491     gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1492     return g->mainbar;
1493 }
1494
1495 static
1496 GtkWidget* create_window () {
1497     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1498     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1499     gtk_widget_set_name (window, "Uzbl browser");
1500     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1501
1502     return window;
1503 }
1504
1505 static void
1506 add_binding (const gchar *key, const gchar *act) {
1507     char **parts = g_strsplit(act, " ", 2);
1508     Action *action;
1509
1510     if (!parts)
1511         return;
1512
1513     //Debug:
1514     if (uzbl.state.verbose)
1515         printf ("Binding %-10s : %s\n", key, act);
1516     action = new_action(parts[0], parts[1]);
1517
1518     if(g_hash_table_lookup(uzbl.bindings, key))
1519         g_hash_table_remove(uzbl.bindings, key);
1520     g_hash_table_insert(uzbl.bindings, g_strdup(key), action);
1521
1522     g_strfreev(parts);
1523 }
1524
1525 static gchar*
1526 get_xdg_var (XDG_Var xdg) {
1527     const gchar* actual_value = getenv (xdg.environmental);
1528     const gchar* home         = getenv ("HOME");
1529
1530     gchar* return_value = str_replace ("~", home, g_strdup (actual_value));
1531
1532     if (! actual_value || strcmp (actual_value, "") == 0) {
1533         if (xdg.default_value) {
1534             return_value = str_replace ("~", home, g_strdup (xdg.default_value));
1535         } else {
1536             return_value = NULL;
1537         }
1538     }
1539
1540     return return_value;
1541 }
1542
1543 static gchar*
1544 find_xdg_file (int xdg_type, char* filename) {
1545     /* xdg_type = 0 => config
1546        xdg_type = 1 => data
1547        xdg_type = 2 => cache*/
1548
1549     gchar* temporary_file   = (char *)malloc (1024);
1550     gchar* temporary_string = NULL;
1551     char*  saveptr;
1552
1553     strcpy (temporary_file, get_xdg_var (XDG[xdg_type]));
1554
1555     strcat (temporary_file, filename);
1556
1557     if (! file_exists (temporary_file) && xdg_type != 2) {
1558         temporary_string = (char *) strtok_r (get_xdg_var (XDG[3 + xdg_type]), ":", &saveptr);
1559         
1560         while (temporary_string && ! file_exists (temporary_file)) {
1561             strcpy (temporary_file, temporary_string);
1562             strcat (temporary_file, filename);
1563             temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1564         }
1565     }
1566
1567     if (file_exists (temporary_file)) {
1568         return temporary_file;
1569     } else {
1570         return NULL;
1571     }
1572 }
1573
1574 static void
1575 settings_init () {
1576     State *s = &uzbl.state;
1577     Network *n = &uzbl.net;
1578
1579     uzbl.behave.reset_command_mode = 1;
1580
1581     if (!s->config_file) {
1582         s->config_file = g_strdup (find_xdg_file (0, "/uzbl/config"));
1583     }
1584
1585     if (s->config_file) {
1586         GIOChannel *chan = NULL;
1587         gchar *readbuf = NULL;
1588         gsize len;
1589
1590         chan = g_io_channel_new_file(s->config_file, "r", NULL);
1591
1592         if (chan) {
1593             while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1594                     == G_IO_STATUS_NORMAL) {
1595                 parse_cmd_line(readbuf);
1596                 g_free (readbuf);
1597             }
1598
1599             g_io_channel_unref (chan);
1600             if (uzbl.state.verbose)
1601                 printf ("Config %s loaded\n", s->config_file);
1602         } else {
1603             fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1604         }
1605     } else {
1606         if (uzbl.state.verbose)
1607             printf ("No configuration file loaded.\n");
1608     }
1609     if (!uzbl.behave.status_format)
1610         set_var_value("status_format", STATUS_DEFAULT);
1611     if (!uzbl.behave.title_format_long)
1612         set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1613     if (!uzbl.behave.title_format_short)
1614         set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1615
1616
1617     g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1618 }
1619
1620 static gchar*
1621 set_useragent(gchar *val) {
1622     if (*val == ' ') {
1623         g_free(val);
1624         return NULL;
1625     }
1626     gchar *ua = expand_template(val);
1627     if (ua)
1628         g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1629     return ua;
1630 }
1631
1632 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1633     (void) session;
1634     (void) user_data;
1635     if (!uzbl.behave.cookie_handler) return;
1636
1637     gchar * stdout = NULL;
1638     soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1639     GString* args = g_string_new ("");
1640     SoupURI * soup_uri = soup_message_get_uri(msg);
1641     g_string_printf (args, "GET %s %s", soup_uri->host, soup_uri->path);
1642     run_command(uzbl.behave.cookie_handler, args->str, TRUE, &stdout);
1643     if(stdout) {
1644         soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1645     }
1646     g_string_free(args, TRUE);
1647 }
1648
1649 static void
1650 save_cookies (SoupMessage *msg, gpointer user_data){
1651     (void) user_data;
1652     GSList *ck;
1653     char *cookie;
1654     for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1655         cookie = soup_cookie_to_set_cookie_header(ck->data);
1656         GString* args = g_string_new ("");
1657         SoupURI * soup_uri = soup_message_get_uri(msg);
1658         g_string_printf (args, "PUT %s %s \"%s\"", soup_uri->host, soup_uri->path, cookie);
1659         run_command(uzbl.behave.cookie_handler, args->str, FALSE, NULL);
1660         g_string_free(args, TRUE);
1661         free(cookie);
1662     }
1663     g_slist_free(ck);
1664 }
1665
1666
1667 int
1668 main (int argc, char* argv[]) {
1669     gtk_init (&argc, &argv);
1670     if (!g_thread_supported ())
1671         g_thread_init (NULL);
1672
1673     strcpy(uzbl.state.executable_path,argv[0]);
1674
1675     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1676     g_option_context_add_main_entries (context, entries, NULL);
1677     g_option_context_add_group (context, gtk_get_option_group (TRUE));
1678     g_option_context_parse (context, &argc, &argv, NULL);
1679     /* initialize hash table */
1680     uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1681
1682     uzbl.net.soup_session = webkit_get_default_session();
1683     uzbl.state.keycmd = g_string_new("");
1684
1685     if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1686         fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1687     if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1688         fprintf(stderr, "uzbl: error hooking SIGINT\n");
1689
1690     if(uname(&uzbl.state.unameinfo) == -1)
1691         g_printerr("Can't retrieve unameinfo.  Your useragent might appear wrong.\n");
1692
1693     setup_regex();
1694     setup_scanner();
1695     commands_hash ();
1696     make_var_to_name_hash();
1697
1698
1699     uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1700
1701     uzbl.gui.scrolled_win = create_browser();
1702     create_mainbar();
1703
1704     /* initial packing */
1705     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1706     gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1707
1708     uzbl.gui.main_window = create_window ();
1709     gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1710
1711     load_uri (uzbl.gui.web_view, uzbl.state.uri); //TODO: is this needed?
1712
1713     gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1714     gtk_widget_show_all (uzbl.gui.main_window);
1715     uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1716
1717     if (uzbl.state.verbose) {
1718         printf("Uzbl start location: %s\n", argv[0]);
1719         printf("window_id %i\n",(int) uzbl.xwin);
1720         printf("pid %i\n", getpid ());
1721         printf("name: %s\n", uzbl.state.instance_name);
1722     }
1723
1724     uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1725     uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1726     uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1727     uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1728     gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1729
1730     settings_init ();
1731
1732     if (!uzbl.behave.show_status)
1733         gtk_widget_hide(uzbl.gui.mainbar);
1734     else
1735         update_title();
1736
1737     create_stdin();
1738
1739     gtk_main ();
1740     clean_up();
1741
1742     return EXIT_SUCCESS;
1743 }
1744
1745 /* vi: set et ts=4: */