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