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.
7 * Copyright (C) 2006, 2007 Apple Inc.
8 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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.
33 #define LENGTH(x) (sizeof x / sizeof x[0])
34 #define MAX_BINDINGS 256
38 #include <gdk/gdkkeysyms.h>
39 #include <sys/socket.h>
41 #include <sys/types.h>
43 #include <sys/utsname.h>
44 #include <webkit/webkit.h>
52 #include <sys/socket.h>
54 #include <libsoup/soup.h>
61 /* define names and pointers to all config specific variables */
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 { "load_start_handler", (void *)&uzbl.behave.load_start_handler },
80 { "load_commit_handler",(void *)&uzbl.behave.load_commit_handler},
81 { "history_handler", (void *)&uzbl.behave.history_handler },
82 { "download_handler", (void *)&uzbl.behave.download_handler },
83 { "cookie_handler", (void *)&uzbl.behave.cookie_handler },
84 { "fifo_dir", (void *)&uzbl.behave.fifo_dir },
85 { "socket_dir", (void *)&uzbl.behave.socket_dir },
86 { "http_debug", (void *)&uzbl.behave.http_debug },
87 { "default_font_size", (void *)&uzbl.behave.default_font_size },
88 { "minimum_font_size", (void *)&uzbl.behave.minimum_font_size },
89 { "shell_cmd", (void *)&uzbl.behave.shell_cmd },
90 { "proxy_url", (void *)&uzbl.net.proxy_url },
91 { "max_conns", (void *)&uzbl.net.max_conns },
92 { "max_conns_host", (void *)&uzbl.net.max_conns_host },
93 { "useragent", (void *)&uzbl.net.useragent },
95 }, *n2v_p = var_name_to_ptr;
101 { "SHIFT", GDK_SHIFT_MASK }, // shift
102 { "LOCK", GDK_LOCK_MASK }, // capslock or shiftlock, depending on xserver's modmappings
103 { "CONTROL", GDK_CONTROL_MASK }, // control
104 { "MOD1", GDK_MOD1_MASK }, // 4th mod - normally alt but depends on modmappings
105 { "MOD2", GDK_MOD2_MASK }, // 5th mod
106 { "MOD3", GDK_MOD3_MASK }, // 6th mod
107 { "MOD4", GDK_MOD4_MASK }, // 7th mod
108 { "MOD5", GDK_MOD5_MASK }, // 8th mod
109 { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
110 { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
111 { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
112 { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
113 { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
114 { "SUPER", GDK_SUPER_MASK }, // super (since 2.10)
115 { "HYPER", GDK_HYPER_MASK }, // hyper (since 2.10)
116 { "META", GDK_META_MASK }, // meta (since 2.10)
120 /* construct a hash from the var_name_to_ptr array for quick access */
122 make_var_to_name_hash() {
123 uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
125 g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, n2v_p->ptr);
130 /* commandline arguments (set initial values for the state variables) */
131 static GOptionEntry entries[] =
133 { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
134 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, "Whether to print all messages or just errors.", NULL },
135 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
136 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
137 { NULL, 0, 0, 0, NULL, NULL, NULL }
140 typedef void (*Command)(WebKitWebView*, GArray *argv);
142 /* --- UTILITY FUNCTIONS --- */
148 snprintf(tmp, sizeof(tmp), "%i", val);
149 return g_strdup(tmp);
153 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
156 str_replace (const char* search, const char* replace, const char* string) {
157 return g_strjoinv (replace, g_strsplit (string, search, -1));
161 read_file_by_line (gchar *path) {
162 GIOChannel *chan = NULL;
163 gchar *readbuf = NULL;
165 GArray *lines = g_array_new(TRUE, FALSE, sizeof(gchar*));
168 chan = g_io_channel_new_file(path, "r", NULL);
171 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL) == G_IO_STATUS_NORMAL) {
172 const gchar* val = g_strdup (readbuf);
173 g_array_append_val (lines, val);
178 g_io_channel_unref (chan);
180 fprintf(stderr, "File '%s' not be read.\n", path);
187 gchar* parseenv (const char* string) {
188 extern char** environ;
189 gchar* newstring = g_strdup (string);
192 while (environ[i] != NULL) {
193 gchar** env = g_strsplit (environ[i], "=", 0);
194 gchar* envname = malloc (strlen (env[0]) + 1);
196 strcat (envname, "$");
197 strcat (envname, env[0]);
199 newstring = str_replace(envname, env[1], newstring);
202 //g_strfreev (env); - This still breaks uzbl, but shouldn't. The mystery thickens...
210 setup_signal(int signr, sigfunc *shandler) {
211 struct sigaction nh, oh;
213 nh.sa_handler = shandler;
214 sigemptyset(&nh.sa_mask);
217 if(sigaction(signr, &nh, &oh) < 0)
225 if (uzbl.behave.fifo_dir)
226 unlink (uzbl.comm.fifo_path);
227 if (uzbl.behave.socket_dir)
228 unlink (uzbl.comm.socket_path);
230 g_string_free(uzbl.state.keycmd, TRUE);
231 g_hash_table_destroy(uzbl.bindings);
232 g_hash_table_destroy(uzbl.behave.commands);
236 /* --- SIGNAL HANDLER --- */
239 catch_sigterm(int s) {
245 catch_sigint(int s) {
251 /* --- CALLBACKS --- */
254 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
257 (void) navigation_action;
258 (void) policy_decision;
260 const gchar* uri = webkit_network_request_get_uri (request);
261 if (uzbl.state.verbose)
262 printf("New window requested -> %s \n", uri);
263 new_window_load_uri(uri);
268 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
272 if (uzbl.state.selected_url[0]!=0) {
273 if (uzbl.state.verbose)
274 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
275 new_window_load_uri(uzbl.state.selected_url);
277 if (uzbl.state.verbose)
278 printf("New web view -> %s\n","Nothing to open, exiting");
284 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
287 if (uzbl.behave.download_handler) {
288 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
289 if (uzbl.state.verbose)
290 printf("Download -> %s\n",uri);
291 /* if urls not escaped, we may have to escape and quote uri before this call */
292 run_handler(uzbl.behave.download_handler, uri);
297 /* scroll a bar in a given direction */
299 scroll (GtkAdjustment* bar, GArray *argv) {
303 amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
304 if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
305 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
308 static void scroll_begin(WebKitWebView* page, GArray *argv) {
309 (void) page; (void) argv;
310 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
313 static void scroll_end(WebKitWebView* page, GArray *argv) {
314 (void) page; (void) argv;
315 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
316 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
319 static void scroll_vert(WebKitWebView* page, GArray *argv) {
321 scroll(uzbl.gui.bar_v, argv);
324 static void scroll_horz(WebKitWebView* page, GArray *argv) {
326 scroll(uzbl.gui.bar_h, argv);
331 if (!uzbl.behave.show_status) {
332 gtk_widget_hide(uzbl.gui.mainbar);
334 gtk_widget_show(uzbl.gui.mainbar);
340 toggle_status_cb (WebKitWebView* page, GArray *argv) {
344 if (uzbl.behave.show_status) {
345 gtk_widget_hide(uzbl.gui.mainbar);
347 gtk_widget_show(uzbl.gui.mainbar);
349 uzbl.behave.show_status = !uzbl.behave.show_status;
354 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
358 //Set selected_url state variable
359 uzbl.state.selected_url[0] = '\0';
361 strcpy (uzbl.state.selected_url, link);
367 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
371 if (uzbl.gui.main_title)
372 g_free (uzbl.gui.main_title);
373 uzbl.gui.main_title = g_strdup (title);
378 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
381 uzbl.gui.sbar.load_progress = progress;
386 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
390 if (uzbl.behave.load_finish_handler)
391 run_handler(uzbl.behave.load_finish_handler, "");
395 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
399 if (uzbl.behave.load_start_handler)
400 run_handler(uzbl.behave.load_start_handler, "");
404 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
407 free (uzbl.state.uri);
408 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
409 uzbl.state.uri = g_string_free (newuri, FALSE);
410 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
411 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
414 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
415 if (uzbl.behave.load_commit_handler)
416 run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
420 destroy_cb (GtkWidget* widget, gpointer data) {
428 if (uzbl.behave.history_handler) {
430 struct tm * timeinfo;
433 timeinfo = localtime ( &rawtime );
434 strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
435 run_handler(uzbl.behave.history_handler, date);
440 /* VIEW funcs (little webkit wrappers) */
441 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
443 VIEWFUNC(reload_bypass_cache)
444 VIEWFUNC(stop_loading)
451 /* -- command to callback/function map for things we cannot attach to any signals */
453 static struct {char *name; Command command[2];} cmdlist[] =
454 { /* key function no_split */
455 { "back", {view_go_back, 0} },
456 { "forward", {view_go_forward, 0} },
457 { "scroll_vert", {scroll_vert, 0} },
458 { "scroll_horz", {scroll_horz, 0} },
459 { "scroll_begin", {scroll_begin, 0} },
460 { "scroll_end", {scroll_end, 0} },
461 { "reload", {view_reload, 0}, },
462 { "reload_ign_cache", {view_reload_bypass_cache, 0} },
463 { "stop", {view_stop_loading, 0}, },
464 { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?).
465 { "zoom_out", {view_zoom_out, 0}, },
466 { "uri", {load_uri, NOSPLIT} },
467 { "js", {run_js, NOSPLIT} },
468 { "script", {run_external_js, 0} },
469 { "toggle_status", {toggle_status_cb, 0} },
470 { "spawn", {spawn, 0} },
471 { "sh", {spawn_sh, 0} },
472 { "exit", {close_uzbl, 0} },
473 { "search", {search_forward_text, NOSPLIT} },
474 { "search_reverse", {search_reverse_text, NOSPLIT} },
475 { "insert_mode", {toggle_insert_mode, 0} },
476 { "runcmd", {runcmd, NOSPLIT} }
483 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
485 for (i = 0; i < LENGTH(cmdlist); i++)
486 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
489 /* -- CORE FUNCTIONS -- */
492 free_action(gpointer act) {
493 Action *action = (Action*)act;
494 g_free(action->name);
496 g_free(action->param);
501 new_action(const gchar *name, const gchar *param) {
502 Action *action = g_new(Action, 1);
504 action->name = g_strdup(name);
506 action->param = g_strdup(param);
508 action->param = NULL;
514 file_exists (const char * filename) {
515 return (access(filename, F_OK) == 0);
519 toggle_insert_mode(WebKitWebView *page, GArray *argv) {
523 if (argv_idx(argv, 0)) {
524 if (strcmp (argv_idx(argv, 0), "0") == 0) {
525 uzbl.behave.insert_mode = FALSE;
527 uzbl.behave.insert_mode = TRUE;
530 uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
537 load_uri (WebKitWebView *web_view, GArray *argv) {
538 if (argv_idx(argv, 0)) {
539 GString* newuri = g_string_new (argv_idx(argv, 0));
540 if (g_strrstr (argv_idx(argv, 0), "://") == NULL)
541 g_string_prepend (newuri, "http://");
542 /* if we do handle cookies, ask our handler for them */
543 webkit_web_view_load_uri (web_view, newuri->str);
544 g_string_free (newuri, TRUE);
549 run_js (WebKitWebView * web_view, GArray *argv) {
550 if (argv_idx(argv, 0))
551 webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
555 run_external_js (WebKitWebView * web_view, GArray *argv) {
556 if (argv_idx(argv, 0)) {
557 GArray* lines = read_file_by_line (argv_idx (argv, 0));
562 while (line = g_array_index(lines, gchar*, i)) {
564 js = g_strdup (line);
566 gchar* newjs = g_strconcat (js, line, NULL);
572 if (uzbl.state.verbose)
573 printf ("External JavaScript file %s loaded\n", argv_idx(argv, 0));
575 if (argv_idx (argv, 1)) {
576 gchar* newjs = str_replace("%s", argv_idx (argv, 1), js);
579 webkit_web_view_execute_script (web_view, js);
581 g_array_free (lines, TRUE);
586 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
587 if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0'))
588 uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
590 if (uzbl.state.searchtx != NULL) {
591 if (uzbl.state.verbose)
592 printf ("Searching: %s\n", uzbl.state.searchtx);
594 if (g_strcmp0 (uzbl.state.searchtx, uzbl.state.searchold) != 0) {
595 webkit_web_view_unmark_text_matches (page);
596 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
598 if (uzbl.state.searchold != NULL)
599 g_free (uzbl.state.searchold);
601 uzbl.state.searchold = g_strdup (uzbl.state.searchtx);
604 webkit_web_view_set_highlight_text_matches (page, TRUE);
605 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
610 search_forward_text (WebKitWebView *page, GArray *argv) {
611 search_text(page, argv, TRUE);
615 search_reverse_text (WebKitWebView *page, GArray *argv) {
616 search_text(page, argv, FALSE);
620 new_window_load_uri (const gchar * uri) {
621 GString* to_execute = g_string_new ("");
622 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
624 for (i = 0; entries[i].long_name != NULL; i++) {
625 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
626 gchar** str = (gchar**)entries[i].arg_data;
628 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
632 if (uzbl.state.verbose)
633 printf("\n%s\n", to_execute->str);
634 g_spawn_command_line_async (to_execute->str, NULL);
635 g_string_free (to_execute, TRUE);
639 close_uzbl (WebKitWebView *page, GArray *argv) {
645 /* --Statusbar functions-- */
647 build_progressbar_ascii(int percent) {
651 GString *bar = g_string_new("");
653 l = (double)percent*((double)width/100.);
654 l = (int)(l+.5)>=(int)l ? l+.5 : l;
656 for(i=0; i<(int)l; i++)
657 g_string_append(bar, "=");
660 g_string_append(bar, "·");
662 return g_string_free(bar, FALSE);
667 const GScannerConfig scan_config = {
670 ) /* cset_skip_characters */,
675 ) /* cset_identifier_first */,
682 ) /* cset_identifier_nth */,
683 ( "" ) /* cpair_comment_single */,
685 TRUE /* case_sensitive */,
687 FALSE /* skip_comment_multi */,
688 FALSE /* skip_comment_single */,
689 FALSE /* scan_comment_multi */,
690 TRUE /* scan_identifier */,
691 TRUE /* scan_identifier_1char */,
692 FALSE /* scan_identifier_NULL */,
693 TRUE /* scan_symbols */,
694 FALSE /* scan_binary */,
695 FALSE /* scan_octal */,
696 FALSE /* scan_float */,
697 FALSE /* scan_hex */,
698 FALSE /* scan_hex_dollar */,
699 FALSE /* scan_string_sq */,
700 FALSE /* scan_string_dq */,
701 TRUE /* numbers_2_int */,
702 FALSE /* int_2_float */,
703 FALSE /* identifier_2_string */,
704 FALSE /* char_2_token */,
705 FALSE /* symbol_2_token */,
706 TRUE /* scope_0_fallback */,
711 uzbl.scan = g_scanner_new(&scan_config);
712 while(symp->symbol_name) {
713 g_scanner_scope_add_symbol(uzbl.scan, 0,
715 GINT_TO_POINTER(symp->symbol_token));
721 expand_template(const char *template) {
722 if(!template) return NULL;
724 GTokenType token = G_TOKEN_NONE;
725 GString *ret = g_string_new("");
729 g_scanner_input_text(uzbl.scan, template, strlen(template));
730 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
731 token = g_scanner_get_next_token(uzbl.scan);
733 if(token == G_TOKEN_SYMBOL) {
734 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
739 g_markup_printf_escaped("%s", uzbl.state.uri):"");
742 buf = itos(uzbl.gui.sbar.load_progress);
743 g_string_append(ret, buf);
746 case SYM_LOADPRGSBAR:
747 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
748 g_string_append(ret, buf);
754 g_markup_printf_escaped("%s", uzbl.gui.main_title):"");
756 case SYM_SELECTED_URI:
758 uzbl.state.selected_url?
759 g_markup_printf_escaped("%s", uzbl.state.selected_url):"");
762 buf = itos(uzbl.xwin);
764 uzbl.state.instance_name?uzbl.state.instance_name:buf);
769 uzbl.state.keycmd->str ?
770 g_markup_printf_escaped("%s", uzbl.state.keycmd->str):"");
774 uzbl.behave.insert_mode?"[I]":"[C]");
778 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
782 buf = itos(WEBKIT_MAJOR_VERSION);
783 g_string_append(ret, buf);
787 buf = itos(WEBKIT_MINOR_VERSION);
788 g_string_append(ret, buf);
792 buf = itos(WEBKIT_MICRO_VERSION);
793 g_string_append(ret, buf);
797 g_string_append(ret, uzbl.state.unameinfo.sysname);
800 g_string_append(ret, uzbl.state.unameinfo.nodename);
803 g_string_append(ret, uzbl.state.unameinfo.release);
806 g_string_append(ret, uzbl.state.unameinfo.version);
809 g_string_append(ret, uzbl.state.unameinfo.machine);
812 g_string_append(ret, ARCH);
816 g_string_append(ret, uzbl.state.unameinfo.domainname);
820 g_string_append(ret, COMMIT);
826 else if(token == G_TOKEN_INT) {
827 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
828 g_string_append(ret, buf);
831 else if(token == G_TOKEN_IDENTIFIER) {
832 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
834 else if(token == G_TOKEN_CHAR) {
835 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
839 return g_string_free(ret, FALSE);
841 /* --End Statusbar functions-- */
844 sharg_append(GArray *a, const gchar *str) {
845 const gchar *s = (str ? str : "");
846 g_array_append_val(a, s);
849 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
851 run_command (const gchar *command, const guint npre, const gchar **args,
852 const gboolean sync, char **stdout) {
853 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
856 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
857 gchar *pid = itos(getpid());
858 gchar *xwin = itos(uzbl.xwin);
860 sharg_append(a, command);
861 for (i = 0; i < npre; i++) /* add n args before the default vars */
862 sharg_append(a, args[i]);
863 sharg_append(a, uzbl.state.config_file);
864 sharg_append(a, pid);
865 sharg_append(a, xwin);
866 sharg_append(a, uzbl.comm.fifo_path);
867 sharg_append(a, uzbl.comm.socket_path);
868 sharg_append(a, uzbl.state.uri);
869 sharg_append(a, uzbl.gui.main_title);
871 for (i = npre; i < g_strv_length((gchar**)args); i++)
872 sharg_append(a, args[i]);
874 if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
875 NULL, NULL, stdout, NULL, NULL, &err);
876 else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
877 NULL, NULL, NULL, &err);
879 if (uzbl.state.verbose) {
880 GString *s = g_string_new("spawned:");
881 for (i = 0; i < (a->len); i++) {
882 gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
883 g_string_append_printf(s, " %s", qarg);
886 g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
887 printf("%s\n", s->str);
888 g_string_free(s, TRUE);
891 g_printerr("error on run_command: %s\n", err->message);
896 g_array_free (a, TRUE);
901 split_quoted(const gchar* src, const gboolean unquote) {
902 /* split on unquoted space, return array of strings;
903 remove a layer of quotes and backslashes if unquote */
904 if (!src) return NULL;
908 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
909 GString *s = g_string_new ("");
913 for (p = src; *p != '\0'; p++) {
914 if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
915 else if (*p == '\\') { g_string_append_c(s, *p++);
916 g_string_append_c(s, *p); }
917 else if ((*p == '"') && unquote && !sq) dq = !dq;
918 else if (*p == '"' && !sq) { g_string_append_c(s, *p);
920 else if ((*p == '\'') && unquote && !dq) sq = !sq;
921 else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
923 else if ((*p == ' ') && !dq && !sq) {
924 dup = g_strdup(s->str);
925 g_array_append_val(a, dup);
926 g_string_truncate(s, 0);
927 } else g_string_append_c(s, *p);
929 dup = g_strdup(s->str);
930 g_array_append_val(a, dup);
931 ret = (gchar**)a->data;
932 g_array_free (a, FALSE);
933 g_string_free (s, FALSE);
938 spawn(WebKitWebView *web_view, GArray *argv) {
940 //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
941 if (argv_idx(argv, 0)) run_command(argv_idx(argv, 0), 0, (const gchar **) argv->data + sizeof(gchar*), FALSE, NULL);
945 spawn_sh(WebKitWebView *web_view, GArray *argv) {
947 if (!uzbl.behave.shell_cmd) {
948 g_printerr ("spawn_sh: shell_cmd is not set!\n");
953 gchar *spacer = g_strdup("");
954 g_array_insert_val(argv, 1, spacer);
955 gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
957 for (i = 1; i < g_strv_length(cmd); i++)
958 g_array_prepend_val(argv, cmd[i]);
960 if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, (const gchar **) argv->data, FALSE, NULL);
966 parse_command(const char *cmd, const char *param) {
969 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
972 gchar **par = split_quoted(param, TRUE);
973 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
975 if (c[1]) { /* don't split */
976 sharg_append(a, param);
978 for (i = 0; i < g_strv_length(par); i++)
979 sharg_append(a, par[i]);
981 c[0](uzbl.gui.web_view, a);
983 g_array_free (a, TRUE);
986 g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
992 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
993 G_REGEX_OPTIMIZE, 0, NULL);
994 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
995 G_REGEX_OPTIMIZE, 0, NULL);
996 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
997 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
998 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
999 G_REGEX_OPTIMIZE, 0, NULL);
1000 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
1001 G_REGEX_OPTIMIZE, 0, NULL);
1005 get_var_value(gchar *name) {
1008 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1009 if(var_is("uri", name)
1010 || var_is("status_message", name)
1011 || var_is("status_format", name)
1012 || var_is("status_background", name)
1013 || var_is("title_format_short", name)
1014 || var_is("title_format_long", name)
1015 || var_is("modkey", name)
1016 || var_is("load_finish_handler", name)
1017 || var_is("load_start_handler", name)
1018 || var_is("load_commit_handler", name)
1019 || var_is("history_handler", name)
1020 || var_is("download_handler", name)
1021 || var_is("cookie_handler", name)
1022 || var_is("fifo_dir", name)
1023 || var_is("socket_dir", name)
1024 || var_is("shell_cmd", name)
1025 || var_is("proxy_url", name)
1026 || var_is("useragent", name))
1028 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
1029 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
1038 if(*uzbl.net.proxy_url == ' '
1039 || uzbl.net.proxy_url == NULL) {
1040 soup_session_remove_feature_by_type(uzbl.net.soup_session,
1041 (GType) SOUP_SESSION_PROXY_URI);
1044 suri = soup_uri_new(uzbl.net.proxy_url);
1045 g_object_set(G_OBJECT(uzbl.net.soup_session),
1046 SOUP_SESSION_PROXY_URI,
1048 soup_uri_free(suri);
1056 gtk_widget_ref(uzbl.gui.scrolled_win);
1057 gtk_widget_ref(uzbl.gui.mainbar);
1058 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1059 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1061 if(uzbl.behave.status_top) {
1062 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1063 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1066 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1067 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1069 gtk_widget_unref(uzbl.gui.scrolled_win);
1070 gtk_widget_unref(uzbl.gui.mainbar);
1071 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1075 var_is(const char *x, const char *y) {
1076 return (strcmp(x, y) == 0 ? TRUE : FALSE );
1080 set_var_value(gchar *name, gchar *val) {
1084 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1085 if(var_is("status_message", name)
1086 || var_is("status_background", name)
1087 || var_is("status_format", name)
1088 || var_is("title_format_long", name)
1089 || var_is("title_format_short", name)
1090 || var_is("load_finish_handler", name)
1091 || var_is("load_start_handler", name)
1092 || var_is("load_commit_handler", name)
1093 || var_is("history_handler", name)
1094 || var_is("download_handler", name)
1095 || var_is("cookie_handler", name)) {
1101 else if(var_is("uri", name)) {
1104 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1105 g_array_append_val(a, *p);
1106 load_uri(uzbl.gui.web_view, a);
1107 g_array_free(a, TRUE);
1109 else if(var_is("proxy_url", name)) {
1114 else if(var_is("fifo_dir", name)) {
1116 *p = init_fifo(g_strdup(val));
1118 else if(var_is("socket_dir", name)) {
1120 *p = init_socket(g_strdup(val));
1122 else if(var_is("modkey", name)) {
1125 *p = g_utf8_strup(val, -1);
1126 uzbl.behave.modmask = 0;
1127 for (i = 0; modkeys[i].key != NULL; i++) {
1128 if (g_strrstr(*p, modkeys[i].key))
1129 uzbl.behave.modmask |= modkeys[i].mask;
1132 else if(var_is("useragent", name)) {
1134 *p = set_useragent(g_strdup(val));
1136 else if(var_is("shell_cmd", name)) {
1140 /* variables that take int values */
1143 *ip = (int)strtoul(val, &endp, 10);
1145 if(var_is("show_status", name)) {
1148 else if(var_is("always_insert_mode", name)) {
1149 uzbl.behave.insert_mode =
1150 uzbl.behave.always_insert_mode ? TRUE : FALSE;
1153 else if (var_is("max_conns", name)) {
1154 g_object_set(G_OBJECT(uzbl.net.soup_session),
1155 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1157 else if (var_is("max_conns_host", name)) {
1158 g_object_set(G_OBJECT(uzbl.net.soup_session),
1159 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1161 else if (var_is("http_debug", name)) {
1162 //soup_session_remove_feature
1163 // (uzbl.net.soup_session, uzbl.net.soup_logger);
1164 soup_session_remove_feature
1165 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1166 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1167 /*g_free(uzbl.net.soup_logger);*/
1169 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1170 soup_session_add_feature(uzbl.net.soup_session,
1171 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1173 else if (var_is("status_top", name)) {
1176 else if (var_is("default_font_size", name)) {
1177 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1178 g_object_set (G_OBJECT(ws), "default-font-size", *ip, NULL);
1180 else if (var_is("minimum_font_size", name)) {
1181 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1182 g_object_set (G_OBJECT(ws), "minimum-font-size", *ip, NULL);
1190 runcmd(WebKitWebView* page, GArray *argv) {
1192 parse_cmd_line(argv_idx(argv, 0));
1196 parse_cmd_line(const char *ctl_line) {
1200 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1201 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1202 if(tokens[0][0] == 0) {
1203 gchar* value = parseenv (tokens[2]);
1204 set_var_value(tokens[1], value);
1209 printf("Error in command: %s\n", tokens[0]);
1212 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1213 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1214 if(tokens[0][0] == 0) {
1215 get_var_value(tokens[1]);
1219 printf("Error in command: %s\n", tokens[0]);
1222 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1223 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1224 if(tokens[0][0] == 0) {
1225 gchar* value = parseenv (tokens[2]);
1226 add_binding(tokens[1], value);
1231 printf("Error in command: %s\n", tokens[0]);
1234 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1235 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1236 if(tokens[0][0] == 0) {
1237 parse_command(tokens[1], tokens[2]);
1241 printf("Error in command: %s\n", tokens[0]);
1243 /* KEYCMD command */
1244 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1245 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1246 if(tokens[0][0] == 0) {
1247 /* should incremental commands want each individual "keystroke"
1248 sent in a loop or the whole string in one go like now? */
1249 g_string_assign(uzbl.state.keycmd, tokens[1]);
1256 else if( (ctl_line[0] == '#')
1257 || (ctl_line[0] == ' ')
1258 || (ctl_line[0] == '\n'))
1259 ; /* ignore these lines */
1261 printf("Command not understood (%s)\n", ctl_line);
1267 build_stream_name(int type, const gchar* dir) {
1269 State *s = &uzbl.state;
1272 xwin_str = itos((int)uzbl.xwin);
1274 str = g_strdup_printf
1275 ("%s/uzbl_fifo_%s", dir,
1276 s->instance_name ? s->instance_name : xwin_str);
1277 } else if (type == SOCKET) {
1278 str = g_strdup_printf
1279 ("%s/uzbl_socket_%s", dir,
1280 s->instance_name ? s->instance_name : xwin_str );
1287 control_fifo(GIOChannel *gio, GIOCondition condition) {
1288 if (uzbl.state.verbose)
1289 printf("triggered\n");
1294 if (condition & G_IO_HUP)
1295 g_error ("Fifo: Read end of pipe died!\n");
1298 g_error ("Fifo: GIOChannel broke\n");
1300 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1301 if (ret == G_IO_STATUS_ERROR) {
1302 g_error ("Fifo: Error reading: %s\n", err->message);
1306 parse_cmd_line(ctl_line);
1313 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1314 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1315 if (unlink(uzbl.comm.fifo_path) == -1)
1316 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1317 g_free(uzbl.comm.fifo_path);
1318 uzbl.comm.fifo_path = NULL;
1321 if (*dir == ' ') { /* space unsets the variable */
1326 GIOChannel *chan = NULL;
1327 GError *error = NULL;
1328 gchar *path = build_stream_name(FIFO, dir);
1330 if (!file_exists(path)) {
1331 if (mkfifo (path, 0666) == 0) {
1332 // 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.
1333 chan = g_io_channel_new_file(path, "r+", &error);
1335 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1336 if (uzbl.state.verbose)
1337 printf ("init_fifo: created successfully as %s\n", path);
1338 uzbl.comm.fifo_path = path;
1340 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1341 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1342 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1343 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1345 /* if we got this far, there was an error; cleanup */
1346 if (error) g_error_free (error);
1353 control_stdin(GIOChannel *gio, GIOCondition condition) {
1354 gchar *ctl_line = NULL;
1355 gsize ctl_line_len = 0;
1358 if (condition & G_IO_HUP) {
1359 ret = g_io_channel_shutdown (gio, FALSE, NULL);
1363 ret = g_io_channel_read_line(gio, &ctl_line, &ctl_line_len, NULL, NULL);
1364 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1367 parse_cmd_line(ctl_line);
1375 GIOChannel *chan = NULL;
1376 GError *error = NULL;
1378 chan = g_io_channel_unix_new(fileno(stdin));
1380 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1381 g_error ("Stdin: could not add watch\n");
1383 if (uzbl.state.verbose)
1384 printf ("Stdin: watch added successfully\n");
1387 g_error ("Stdin: Error while opening: %s\n", error->message);
1389 if (error) g_error_free (error);
1393 control_socket(GIOChannel *chan) {
1394 struct sockaddr_un remote;
1395 char buffer[512], *ctl_line;
1397 int sock, clientsock, n, done;
1400 sock = g_io_channel_unix_get_fd(chan);
1402 memset (buffer, 0, sizeof (buffer));
1404 t = sizeof (remote);
1405 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1409 memset (temp, 0, sizeof (temp));
1410 n = recv (clientsock, temp, 128, 0);
1412 buffer[strlen (buffer)] = '\0';
1416 strcat (buffer, temp);
1419 if (strcmp (buffer, "\n") < 0) {
1420 buffer[strlen (buffer) - 1] = '\0';
1422 buffer[strlen (buffer)] = '\0';
1425 ctl_line = g_strdup(buffer);
1426 parse_cmd_line (ctl_line);
1429 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1430 GError *error = NULL;
1433 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1434 if (ret == G_IO_STATUS_ERROR)
1435 g_error ("Error reading: %s\n", error->message);
1437 printf("Got line %s (%u bytes) \n",ctl_line, len);
1439 parse_line(ctl_line);
1447 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1448 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1449 if (unlink(uzbl.comm.socket_path) == -1)
1450 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1451 g_free(uzbl.comm.socket_path);
1452 uzbl.comm.socket_path = NULL;
1460 GIOChannel *chan = NULL;
1462 struct sockaddr_un local;
1463 gchar *path = build_stream_name(SOCKET, dir);
1465 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1467 local.sun_family = AF_UNIX;
1468 strcpy (local.sun_path, path);
1469 unlink (local.sun_path);
1471 len = strlen (local.sun_path) + sizeof (local.sun_family);
1472 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1473 if (uzbl.state.verbose)
1474 printf ("init_socket: opened in %s\n", path);
1477 if( (chan = g_io_channel_unix_new(sock)) ) {
1478 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1479 uzbl.comm.socket_path = path;
1482 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1484 /* if we got this far, there was an error; cleanup */
1491 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1492 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1494 // this function may be called very early when the templates are not set (yet), hence the checks
1496 update_title (void) {
1497 Behaviour *b = &uzbl.behave;
1500 if (b->show_status) {
1501 if (b->title_format_short) {
1502 parsed = expand_template(b->title_format_short);
1503 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1506 if (b->status_format) {
1507 parsed = expand_template(b->status_format);
1508 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1511 if (b->status_background) {
1513 gdk_color_parse (b->status_background, &color);
1514 //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)
1515 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1518 if (b->title_format_long) {
1519 parsed = expand_template(b->title_format_long);
1520 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1527 key_press_cb (GtkWidget* window, GdkEventKey* event)
1529 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1533 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1534 || 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)
1537 /* turn off insert mode (if always_insert_mode is not used) */
1538 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1539 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1544 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1547 if (event->keyval == GDK_Escape) {
1548 g_string_truncate(uzbl.state.keycmd, 0);
1553 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1554 if (event->keyval == GDK_Insert) {
1556 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1557 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1559 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1562 g_string_append (uzbl.state.keycmd, str);
1569 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1570 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1574 gboolean key_ret = FALSE;
1575 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1577 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1579 run_keycmd(key_ret);
1581 if (key_ret) return (!uzbl.behave.insert_mode);
1586 run_keycmd(const gboolean key_ret) {
1587 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1589 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1590 g_string_truncate(uzbl.state.keycmd, 0);
1591 parse_command(action->name, action->param);
1595 /* try if it's an incremental keycmd or one that takes args, and run it */
1596 GString* short_keys = g_string_new ("");
1597 GString* short_keys_inc = g_string_new ("");
1599 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1600 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1601 g_string_assign(short_keys_inc, short_keys->str);
1602 g_string_append_c(short_keys, '_');
1603 g_string_append_c(short_keys_inc, '*');
1605 gboolean exec_now = FALSE;
1606 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1607 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1608 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1609 if (key_ret) { /* just quit the incremental command on return */
1610 g_string_truncate(uzbl.state.keycmd, 0);
1612 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1616 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1617 GString* actionname = g_string_new ("");
1618 GString* actionparam = g_string_new ("");
1619 g_string_erase (parampart, 0, i+1);
1621 g_string_printf (actionname, action->name, parampart->str);
1623 g_string_printf (actionparam, action->param, parampart->str);
1624 parse_command(actionname->str, actionparam->str);
1625 g_string_free (actionname, TRUE);
1626 g_string_free (actionparam, TRUE);
1627 g_string_free (parampart, TRUE);
1629 g_string_truncate(uzbl.state.keycmd, 0);
1633 g_string_truncate(short_keys, short_keys->len - 1);
1635 g_string_free (short_keys, TRUE);
1636 g_string_free (short_keys_inc, TRUE);
1643 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1644 //main_window_ref = g_object_ref(scrolled_window);
1645 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
1647 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1648 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1650 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1651 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1652 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1653 g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1654 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1655 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1656 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1657 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1658 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1659 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1661 return scrolled_window;
1668 g->mainbar = gtk_hbox_new (FALSE, 0);
1670 /* keep a reference to the bar so we can re-pack it at runtime*/
1671 //sbar_ref = g_object_ref(g->mainbar);
1673 g->mainbar_label = gtk_label_new ("");
1674 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1675 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1676 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1677 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1678 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1683 GtkWidget* create_window () {
1684 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1685 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1686 gtk_widget_set_name (window, "Uzbl browser");
1687 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1688 g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK (key_press_cb), NULL);
1694 run_handler (const gchar *act, const gchar *args) {
1695 char **parts = g_strsplit(act, " ", 2);
1697 else if ((g_strcmp0(parts[0], "spawn") == 0)
1698 || (g_strcmp0(parts[0], "sh") == 0)) {
1700 GString *a = g_string_new ("");
1702 spawnparts = split_quoted(parts[1], FALSE);
1703 g_string_append_printf(a, "%s", spawnparts[0]);
1704 if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1705 for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1706 g_string_append_printf(a, " %s", spawnparts[i]);
1707 parse_command(parts[0], a->str);
1708 g_string_free (a, TRUE);
1709 g_strfreev (spawnparts);
1711 parse_command(parts[0], parts[1]);
1716 add_binding (const gchar *key, const gchar *act) {
1717 char **parts = g_strsplit(act, " ", 2);
1724 if (uzbl.state.verbose)
1725 printf ("Binding %-10s : %s\n", key, act);
1726 action = new_action(parts[0], parts[1]);
1728 if(g_hash_table_lookup(uzbl.bindings, key))
1729 g_hash_table_remove(uzbl.bindings, key);
1730 g_hash_table_insert(uzbl.bindings, g_strdup(key), action);
1736 get_xdg_var (XDG_Var xdg) {
1737 const gchar* actual_value = getenv (xdg.environmental);
1738 const gchar* home = getenv ("HOME");
1740 gchar* return_value = str_replace ("~", home, g_strdup (actual_value));
1742 if (! actual_value || strcmp (actual_value, "") == 0) {
1743 if (xdg.default_value) {
1744 return_value = str_replace ("~", home, g_strdup (xdg.default_value));
1746 return_value = NULL;
1750 return return_value;
1754 find_xdg_file (int xdg_type, char* filename) {
1755 /* xdg_type = 0 => config
1756 xdg_type = 1 => data
1757 xdg_type = 2 => cache*/
1759 gchar* temporary_file = (char *)malloc (1024);
1760 gchar* temporary_string = NULL;
1763 strcpy (temporary_file, get_xdg_var (XDG[xdg_type]));
1765 strcat (temporary_file, filename);
1767 if (! file_exists (temporary_file) && xdg_type != 2) {
1768 temporary_string = (char *) strtok_r (get_xdg_var (XDG[3 + xdg_type]), ":", &saveptr);
1770 while (temporary_string && ! file_exists (temporary_file)) {
1771 strcpy (temporary_file, temporary_string);
1772 strcat (temporary_file, filename);
1773 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1777 if (file_exists (temporary_file)) {
1778 return temporary_file;
1786 State *s = &uzbl.state;
1787 Network *n = &uzbl.net;
1789 uzbl.behave.reset_command_mode = 1;
1791 if (!s->config_file) {
1792 s->config_file = g_strdup (find_xdg_file (0, "/uzbl/config"));
1795 if (s->config_file) {
1796 GIOChannel *chan = NULL;
1797 gchar *readbuf = NULL;
1800 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1803 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1804 == G_IO_STATUS_NORMAL) {
1805 parse_cmd_line(readbuf);
1809 g_io_channel_unref (chan);
1810 if (uzbl.state.verbose)
1811 printf ("Config %s loaded\n", s->config_file);
1813 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1816 if (uzbl.state.verbose)
1817 printf ("No configuration file loaded.\n");
1819 if (!uzbl.behave.status_format)
1820 set_var_value("status_format", STATUS_DEFAULT);
1821 if (!uzbl.behave.title_format_long)
1822 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1823 if (!uzbl.behave.title_format_short)
1824 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1827 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1831 set_useragent(gchar *val) {
1836 gchar *ua = expand_template(val);
1838 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1842 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1845 if (!uzbl.behave.cookie_handler) return;
1847 gchar * stdout = NULL;
1848 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1849 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1850 gchar *action = g_strdup ("GET");
1851 SoupURI * soup_uri = soup_message_get_uri(msg);
1852 sharg_append(a, action);
1853 sharg_append(a, soup_uri->host);
1854 sharg_append(a, soup_uri->path);
1855 run_command(uzbl.behave.cookie_handler, 0, (const gchar **) a->data, TRUE, &stdout); /* TODO: use handler */
1856 //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */
1858 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1861 g_array_free(a, TRUE);
1865 save_cookies (SoupMessage *msg, gpointer user_data){
1869 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1870 cookie = soup_cookie_to_set_cookie_header(ck->data);
1871 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1872 SoupURI * soup_uri = soup_message_get_uri(msg);
1873 gchar *action = strdup("PUT");
1874 sharg_append(a, action);
1875 sharg_append(a, soup_uri->host);
1876 sharg_append(a, soup_uri->path);
1877 sharg_append(a, cookie);
1878 run_command(uzbl.behave.cookie_handler, 0, (const gchar **) a->data, FALSE, NULL);
1881 g_array_free(a, TRUE);
1887 main (int argc, char* argv[]) {
1888 gtk_init (&argc, &argv);
1889 if (!g_thread_supported ())
1890 g_thread_init (NULL);
1892 strcpy(uzbl.state.executable_path,argv[0]);
1894 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1895 g_option_context_add_main_entries (context, entries, NULL);
1896 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1897 g_option_context_parse (context, &argc, &argv, NULL);
1898 /* initialize hash table */
1899 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1901 uzbl.net.soup_session = webkit_get_default_session();
1902 uzbl.state.keycmd = g_string_new("");
1904 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1905 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1906 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1907 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1909 if(uname(&uzbl.state.unameinfo) == -1)
1910 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1915 make_var_to_name_hash();
1917 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1919 uzbl.gui.scrolled_win = create_browser();
1922 /* initial packing */
1923 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1924 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1926 uzbl.gui.main_window = create_window ();
1927 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1929 //load_uri (uzbl.gui.web_view, uzbl.state.uri); //TODO: is this needed?
1931 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1932 gtk_widget_show_all (uzbl.gui.main_window);
1933 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1935 if (uzbl.state.verbose) {
1936 printf("Uzbl start location: %s\n", argv[0]);
1937 printf("window_id %i\n",(int) uzbl.xwin);
1938 printf("pid %i\n", getpid ());
1939 printf("name: %s\n", uzbl.state.instance_name);
1942 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1943 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1944 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1945 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1946 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1950 if (!uzbl.behave.show_status)
1951 gtk_widget_hide(uzbl.gui.mainbar);
1957 //if(uzbl.state.uri)
1958 // load_uri (uzbl.gui.web_view, uzbl.state.uri);
1964 return EXIT_SUCCESS;
1967 /* vi: set et ts=4: */