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) {
160 buf = g_strsplit (string, search, -1);
161 ret = g_strjoinv (replace, buf);
168 setup_signal(int signr, sigfunc *shandler) {
169 struct sigaction nh, oh;
171 nh.sa_handler = shandler;
172 sigemptyset(&nh.sa_mask);
175 if(sigaction(signr, &nh, &oh) < 0)
183 if (uzbl.behave.fifo_dir)
184 unlink (uzbl.comm.fifo_path);
185 if (uzbl.behave.socket_dir)
186 unlink (uzbl.comm.socket_path);
188 g_free(uzbl.state.executable_path);
189 g_string_free(uzbl.state.keycmd, TRUE);
190 g_hash_table_destroy(uzbl.bindings);
191 g_hash_table_destroy(uzbl.behave.commands);
195 /* --- SIGNAL HANDLER --- */
198 catch_sigterm(int s) {
204 catch_sigint(int s) {
210 /* --- CALLBACKS --- */
213 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
216 (void) navigation_action;
217 (void) policy_decision;
219 const gchar* uri = webkit_network_request_get_uri (request);
220 if (uzbl.state.verbose)
221 printf("New window requested -> %s \n", uri);
222 new_window_load_uri(uri);
227 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
231 if (uzbl.state.selected_url != NULL) {
232 if (uzbl.state.verbose)
233 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
234 new_window_load_uri(uzbl.state.selected_url);
236 if (uzbl.state.verbose)
237 printf("New web view -> %s\n","Nothing to open, exiting");
243 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
246 if (uzbl.behave.download_handler) {
247 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
248 if (uzbl.state.verbose)
249 printf("Download -> %s\n",uri);
250 /* if urls not escaped, we may have to escape and quote uri before this call */
251 run_handler(uzbl.behave.download_handler, uri);
256 /* scroll a bar in a given direction */
258 scroll (GtkAdjustment* bar, GArray *argv) {
262 amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
263 if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
264 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
267 static void scroll_begin(WebKitWebView* page, GArray *argv) {
268 (void) page; (void) argv;
269 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
272 static void scroll_end(WebKitWebView* page, GArray *argv) {
273 (void) page; (void) argv;
274 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
275 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
278 static void scroll_vert(WebKitWebView* page, GArray *argv) {
280 scroll(uzbl.gui.bar_v, argv);
283 static void scroll_horz(WebKitWebView* page, GArray *argv) {
285 scroll(uzbl.gui.bar_h, argv);
290 if (!uzbl.behave.show_status) {
291 gtk_widget_hide(uzbl.gui.mainbar);
293 gtk_widget_show(uzbl.gui.mainbar);
299 toggle_status_cb (WebKitWebView* page, GArray *argv) {
303 if (uzbl.behave.show_status) {
304 gtk_widget_hide(uzbl.gui.mainbar);
306 gtk_widget_show(uzbl.gui.mainbar);
308 uzbl.behave.show_status = !uzbl.behave.show_status;
313 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
317 //Set selected_url state variable
318 g_free(uzbl.state.selected_url);
319 uzbl.state.selected_url = NULL;
321 uzbl.state.selected_url = g_strdup(link);
327 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
331 if (uzbl.gui.main_title)
332 g_free (uzbl.gui.main_title);
333 uzbl.gui.main_title = g_strdup (title);
338 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
341 uzbl.gui.sbar.load_progress = progress;
346 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
350 if (uzbl.behave.load_finish_handler)
351 run_handler(uzbl.behave.load_finish_handler, "");
355 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
359 if (uzbl.behave.load_start_handler)
360 run_handler(uzbl.behave.load_start_handler, "");
364 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
367 free (uzbl.state.uri);
368 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
369 uzbl.state.uri = g_string_free (newuri, FALSE);
370 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
371 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
374 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
375 if (uzbl.behave.load_commit_handler)
376 run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
380 destroy_cb (GtkWidget* widget, gpointer data) {
388 if (uzbl.behave.history_handler) {
390 struct tm * timeinfo;
393 timeinfo = localtime ( &rawtime );
394 strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
395 run_handler(uzbl.behave.history_handler, date);
400 /* VIEW funcs (little webkit wrappers) */
401 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
403 VIEWFUNC(reload_bypass_cache)
404 VIEWFUNC(stop_loading)
411 /* -- command to callback/function map for things we cannot attach to any signals */
414 static struct {char *name; Command command[2];} cmdlist[] =
415 { /* key function no_split */
416 { "back", {view_go_back, 0} },
417 { "forward", {view_go_forward, 0} },
418 { "scroll_vert", {scroll_vert, 0} },
419 { "scroll_horz", {scroll_horz, 0} },
420 { "scroll_begin", {scroll_begin, 0} },
421 { "scroll_end", {scroll_end, 0} },
422 { "reload", {view_reload, 0}, },
423 { "reload_ign_cache", {view_reload_bypass_cache, 0} },
424 { "stop", {view_stop_loading, 0}, },
425 { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?).
426 { "zoom_out", {view_zoom_out, 0}, },
427 { "uri", {load_uri, NOSPLIT} },
428 { "script", {run_js, NOSPLIT} },
429 { "toggle_status", {toggle_status_cb, 0} },
430 { "spawn", {spawn, 0} },
431 { "sh", {spawn_sh, 0} },
432 { "exit", {close_uzbl, 0} },
433 { "search", {search_forward_text, NOSPLIT} },
434 { "search_reverse", {search_reverse_text, NOSPLIT} },
435 { "insert_mode", {set_insert_mode, 0} },
436 { "runcmd", {runcmd, NOSPLIT} }
443 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
445 for (i = 0; i < LENGTH(cmdlist); i++)
446 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
449 /* -- CORE FUNCTIONS -- */
452 free_action(gpointer act) {
453 Action *action = (Action*)act;
454 g_free(action->name);
456 g_free(action->param);
461 new_action(const gchar *name, const gchar *param) {
462 Action *action = g_new(Action, 1);
464 action->name = g_strdup(name);
466 action->param = g_strdup(param);
468 action->param = NULL;
474 file_exists (const char * filename) {
475 return (access(filename, F_OK) == 0);
479 set_insert_mode(WebKitWebView *page, GArray *argv) {
483 uzbl.behave.insert_mode = TRUE;
488 load_uri (WebKitWebView *web_view, GArray *argv) {
489 if (argv_idx(argv, 0)) {
490 GString* newuri = g_string_new (argv_idx(argv, 0));
491 if (g_strrstr (argv_idx(argv, 0), "://") == NULL)
492 g_string_prepend (newuri, "http://");
493 /* if we do handle cookies, ask our handler for them */
494 webkit_web_view_load_uri (web_view, newuri->str);
495 g_string_free (newuri, TRUE);
500 run_js (WebKitWebView * web_view, GArray *argv) {
501 if (argv_idx(argv, 0))
502 webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
506 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
507 if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0'))
508 uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
510 if (uzbl.state.searchtx != NULL) {
511 if (uzbl.state.verbose)
512 printf ("Searching: %s\n", uzbl.state.searchtx);
513 webkit_web_view_unmark_text_matches (page);
514 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
515 webkit_web_view_set_highlight_text_matches (page, TRUE);
516 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
517 g_free(uzbl.state.searchtx);
518 uzbl.state.searchtx = NULL;
523 search_forward_text (WebKitWebView *page, GArray *argv) {
524 search_text(page, argv, TRUE);
528 search_reverse_text (WebKitWebView *page, GArray *argv) {
529 search_text(page, argv, FALSE);
533 new_window_load_uri (const gchar * uri) {
534 GString* to_execute = g_string_new ("");
535 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
537 for (i = 0; entries[i].long_name != NULL; i++) {
538 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
539 gchar** str = (gchar**)entries[i].arg_data;
541 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
545 if (uzbl.state.verbose)
546 printf("\n%s\n", to_execute->str);
547 g_spawn_command_line_async (to_execute->str, NULL);
548 g_string_free (to_execute, TRUE);
552 close_uzbl (WebKitWebView *page, GArray *argv) {
558 /* --Statusbar functions-- */
560 build_progressbar_ascii(int percent) {
564 GString *bar = g_string_new("");
566 l = (double)percent*((double)width/100.);
567 l = (int)(l+.5)>=(int)l ? l+.5 : l;
569 for(i=0; i<(int)l; i++)
570 g_string_append(bar, "=");
573 g_string_append(bar, "·");
575 return g_string_free(bar, FALSE);
580 const GScannerConfig scan_config = {
583 ) /* cset_skip_characters */,
588 ) /* cset_identifier_first */,
595 ) /* cset_identifier_nth */,
596 ( "" ) /* cpair_comment_single */,
598 TRUE /* case_sensitive */,
600 FALSE /* skip_comment_multi */,
601 FALSE /* skip_comment_single */,
602 FALSE /* scan_comment_multi */,
603 TRUE /* scan_identifier */,
604 TRUE /* scan_identifier_1char */,
605 FALSE /* scan_identifier_NULL */,
606 TRUE /* scan_symbols */,
607 FALSE /* scan_binary */,
608 FALSE /* scan_octal */,
609 FALSE /* scan_float */,
610 FALSE /* scan_hex */,
611 FALSE /* scan_hex_dollar */,
612 FALSE /* scan_string_sq */,
613 FALSE /* scan_string_dq */,
614 TRUE /* numbers_2_int */,
615 FALSE /* int_2_float */,
616 FALSE /* identifier_2_string */,
617 FALSE /* char_2_token */,
618 FALSE /* symbol_2_token */,
619 TRUE /* scope_0_fallback */,
624 uzbl.scan = g_scanner_new(&scan_config);
625 while(symp->symbol_name) {
626 g_scanner_scope_add_symbol(uzbl.scan, 0,
628 GINT_TO_POINTER(symp->symbol_token));
634 expand_template(const char *template) {
635 if(!template) return NULL;
637 GTokenType token = G_TOKEN_NONE;
638 GString *ret = g_string_new("");
642 g_scanner_input_text(uzbl.scan, template, strlen(template));
643 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
644 token = g_scanner_get_next_token(uzbl.scan);
646 if(token == G_TOKEN_SYMBOL) {
647 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
650 buf = uzbl.state.uri?
651 g_markup_printf_escaped("%s", uzbl.state.uri) :
653 g_string_append(ret, buf);
657 buf = itos(uzbl.gui.sbar.load_progress);
658 g_string_append(ret, buf);
661 case SYM_LOADPRGSBAR:
662 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
663 g_string_append(ret, buf);
667 buf = uzbl.gui.main_title?
668 g_markup_printf_escaped("%s", uzbl.gui.main_title) :
670 g_string_append(ret, buf);
673 case SYM_SELECTED_URI:
674 buf = uzbl.state.selected_url?
675 g_markup_printf_escaped("%s", uzbl.state.selected_url) :
677 g_string_append(ret, buf);
681 buf = itos(uzbl.xwin);
683 uzbl.state.instance_name?uzbl.state.instance_name:buf);
687 buf = uzbl.state.keycmd->str?
688 g_markup_printf_escaped("%s", uzbl.state.keycmd->str) :
690 g_string_append(ret, buf);
695 uzbl.behave.insert_mode?"[I]":"[C]");
699 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
703 buf = itos(WEBKIT_MAJOR_VERSION);
704 g_string_append(ret, buf);
708 buf = itos(WEBKIT_MINOR_VERSION);
709 g_string_append(ret, buf);
713 buf = itos(WEBKIT_MICRO_VERSION);
714 g_string_append(ret, buf);
718 g_string_append(ret, uzbl.state.unameinfo.sysname);
721 g_string_append(ret, uzbl.state.unameinfo.nodename);
724 g_string_append(ret, uzbl.state.unameinfo.release);
727 g_string_append(ret, uzbl.state.unameinfo.version);
730 g_string_append(ret, uzbl.state.unameinfo.machine);
733 g_string_append(ret, ARCH);
737 g_string_append(ret, uzbl.state.unameinfo.domainname);
741 g_string_append(ret, COMMIT);
747 else if(token == G_TOKEN_INT) {
748 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
749 g_string_append(ret, buf);
752 else if(token == G_TOKEN_IDENTIFIER) {
753 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
755 else if(token == G_TOKEN_CHAR) {
756 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
760 return g_string_free(ret, FALSE);
762 /* --End Statusbar functions-- */
765 sharg_append(GArray *a, const gchar *str) {
766 const gchar *s = (str ? str : "");
767 g_array_append_val(a, s);
770 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
772 run_command (const gchar *command, const guint npre, const gchar **args,
773 const gboolean sync, char **stdout) {
774 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
777 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
778 gchar *pid = itos(getpid());
779 gchar *xwin = itos(uzbl.xwin);
781 sharg_append(a, command);
782 for (i = 0; i < npre; i++) /* add n args before the default vars */
783 sharg_append(a, args[i]);
784 sharg_append(a, uzbl.state.config_file);
785 sharg_append(a, pid);
786 sharg_append(a, xwin);
787 sharg_append(a, uzbl.comm.fifo_path);
788 sharg_append(a, uzbl.comm.socket_path);
789 sharg_append(a, uzbl.state.uri);
790 sharg_append(a, uzbl.gui.main_title);
792 for (i = npre; i < g_strv_length((gchar**)args); i++)
793 sharg_append(a, args[i]);
795 if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
796 NULL, NULL, stdout, NULL, NULL, &err);
797 else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
798 NULL, NULL, NULL, &err);
800 if (uzbl.state.verbose) {
801 GString *s = g_string_new("spawned:");
802 for (i = 0; i < (a->len); i++) {
803 gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
804 g_string_append_printf(s, " %s", qarg);
807 g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
808 printf("%s\n", s->str);
809 g_string_free(s, TRUE);
812 g_printerr("error on run_command: %s\n", err->message);
817 g_array_free (a, TRUE);
822 split_quoted(const gchar* src, const gboolean unquote) {
823 /* split on unquoted space, return array of strings;
824 remove a layer of quotes and backslashes if unquote */
825 if (!src) return NULL;
829 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
830 GString *s = g_string_new ("");
834 for (p = src; *p != '\0'; p++) {
835 if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
836 else if (*p == '\\') { g_string_append_c(s, *p++);
837 g_string_append_c(s, *p); }
838 else if ((*p == '"') && unquote && !sq) dq = !dq;
839 else if (*p == '"' && !sq) { g_string_append_c(s, *p);
841 else if ((*p == '\'') && unquote && !dq) sq = !sq;
842 else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
844 else if ((*p == ' ') && !dq && !sq) {
845 dup = g_strdup(s->str);
846 g_array_append_val(a, dup);
847 g_string_truncate(s, 0);
848 } else g_string_append_c(s, *p);
850 dup = g_strdup(s->str);
851 g_array_append_val(a, dup);
852 ret = (gchar**)a->data;
853 g_array_free (a, FALSE);
854 g_string_free (s, FALSE);
859 spawn(WebKitWebView *web_view, GArray *argv) {
861 //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
862 if (argv_idx(argv, 0)) run_command(argv_idx(argv, 0), 0, argv->data + sizeof(gchar*), FALSE, NULL);
866 spawn_sh(WebKitWebView *web_view, GArray *argv) {
868 if (!uzbl.behave.shell_cmd) {
869 g_printerr ("spawn_sh: shell_cmd is not set!\n");
874 gchar *spacer = g_strdup("");
875 g_array_insert_val(argv, 1, spacer);
876 gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
878 for (i = 1; i < g_strv_length(cmd); i++)
879 g_array_prepend_val(argv, cmd[i]);
881 if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, argv->data, FALSE, NULL);
887 parse_command(const char *cmd, const char *param) {
890 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
893 gchar **par = split_quoted(param, TRUE);
894 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
896 if (c[1]) { /* don't split */
897 sharg_append(a, param);
899 for (i = 0; i < g_strv_length(par); i++)
900 sharg_append(a, par[i]);
902 c[0](uzbl.gui.web_view, a);
904 g_array_free (a, TRUE);
907 g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
913 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
914 G_REGEX_OPTIMIZE, 0, NULL);
915 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
916 G_REGEX_OPTIMIZE, 0, NULL);
917 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
918 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
919 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
920 G_REGEX_OPTIMIZE, 0, NULL);
921 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
922 G_REGEX_OPTIMIZE, 0, NULL);
926 get_var_value(gchar *name) {
929 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
930 if(var_is("uri", name)
931 || var_is("status_message", name)
932 || var_is("status_format", name)
933 || var_is("status_background", name)
934 || var_is("title_format_short", name)
935 || var_is("title_format_long", name)
936 || var_is("modkey", name)
937 || var_is("load_finish_handler", name)
938 || var_is("load_start_handler", name)
939 || var_is("load_commit_handler", name)
940 || var_is("history_handler", name)
941 || var_is("download_handler", name)
942 || var_is("cookie_handler", name)
943 || var_is("fifo_dir", name)
944 || var_is("socket_dir", name)
945 || var_is("shell_cmd", name)
946 || var_is("proxy_url", name)
947 || var_is("useragent", name))
949 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
950 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
959 if(*uzbl.net.proxy_url == ' '
960 || uzbl.net.proxy_url == NULL) {
961 soup_session_remove_feature_by_type(uzbl.net.soup_session,
962 (GType) SOUP_SESSION_PROXY_URI);
965 suri = soup_uri_new(uzbl.net.proxy_url);
966 g_object_set(G_OBJECT(uzbl.net.soup_session),
967 SOUP_SESSION_PROXY_URI,
977 gtk_widget_ref(uzbl.gui.scrolled_win);
978 gtk_widget_ref(uzbl.gui.mainbar);
979 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
980 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
982 if(uzbl.behave.status_top) {
983 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
984 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
987 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
988 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
990 gtk_widget_unref(uzbl.gui.scrolled_win);
991 gtk_widget_unref(uzbl.gui.mainbar);
992 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
996 var_is(const char *x, const char *y) {
997 return (strcmp(x, y) == 0 ? TRUE : FALSE );
1001 set_var_value(gchar *name, gchar *val) {
1006 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1007 if(var_is("status_message", name)
1008 || var_is("status_background", name)
1009 || var_is("status_format", name)
1010 || var_is("title_format_long", name)
1011 || var_is("title_format_short", name)
1012 || var_is("load_finish_handler", name)
1013 || var_is("load_start_handler", name)
1014 || var_is("load_commit_handler", name)
1015 || var_is("history_handler", name)
1016 || var_is("download_handler", name)
1017 || var_is("cookie_handler", name)) {
1023 else if(var_is("uri", name)) {
1026 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1027 g_array_append_val(a, *p);
1028 load_uri(uzbl.gui.web_view, a);
1029 g_array_free(a, TRUE);
1031 else if(var_is("proxy_url", name)) {
1036 else if(var_is("fifo_dir", name)) {
1038 buf = init_fifo(val);
1039 *p = buf?buf:g_strdup("");
1041 else if(var_is("socket_dir", name)) {
1043 buf = init_socket(val);
1044 *p = buf?buf:g_strdup("");
1046 else if(var_is("modkey", name)) {
1049 *p = g_utf8_strup(val, -1);
1050 uzbl.behave.modmask = 0;
1051 for (i = 0; modkeys[i].key != NULL; i++) {
1052 if (g_strrstr(*p, modkeys[i].key))
1053 uzbl.behave.modmask |= modkeys[i].mask;
1056 else if(var_is("useragent", name)) {
1058 buf = set_useragent(val);
1059 *p = buf?buf:g_strdup("");
1061 else if(var_is("shell_cmd", name)) {
1065 /* variables that take int values */
1068 *ip = (int)strtoul(val, &endp, 10);
1070 if(var_is("show_status", name)) {
1073 else if(var_is("always_insert_mode", name)) {
1074 uzbl.behave.insert_mode =
1075 uzbl.behave.always_insert_mode ? TRUE : FALSE;
1078 else if (var_is("max_conns", name)) {
1079 g_object_set(G_OBJECT(uzbl.net.soup_session),
1080 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1082 else if (var_is("max_conns_host", name)) {
1083 g_object_set(G_OBJECT(uzbl.net.soup_session),
1084 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1086 else if (var_is("http_debug", name)) {
1087 soup_session_remove_feature
1088 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1089 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1090 /*g_free(uzbl.net.soup_logger);*/
1092 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1093 soup_session_add_feature(uzbl.net.soup_session,
1094 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1096 else if (var_is("status_top", name)) {
1099 else if (var_is("default_font_size", name)) {
1100 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1101 g_object_set (G_OBJECT(ws), "default-font-size", *ip, NULL);
1103 else if (var_is("minimum_font_size", name)) {
1104 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1105 g_object_set (G_OBJECT(ws), "minimum-font-size", *ip, NULL);
1113 runcmd(WebKitWebView* page, GArray *argv) {
1115 parse_cmd_line(argv_idx(argv, 0));
1119 parse_cmd_line(const char *ctl_line) {
1123 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1124 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1125 if(tokens[0][0] == 0) {
1126 set_var_value(tokens[1], tokens[2]);
1130 printf("Error in command: %s\n", tokens[0]);
1133 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1134 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1135 if(tokens[0][0] == 0) {
1136 get_var_value(tokens[1]);
1140 printf("Error in command: %s\n", tokens[0]);
1143 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1144 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1145 if(tokens[0][0] == 0) {
1146 add_binding(tokens[1], tokens[2]);
1150 printf("Error in command: %s\n", tokens[0]);
1153 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1154 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1155 if(tokens[0][0] == 0) {
1156 parse_command(tokens[1], tokens[2]);
1160 printf("Error in command: %s\n", tokens[0]);
1162 /* KEYCMD command */
1163 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1164 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1165 if(tokens[0][0] == 0) {
1166 /* should incremental commands want each individual "keystroke"
1167 sent in a loop or the whole string in one go like now? */
1168 g_string_assign(uzbl.state.keycmd, tokens[1]);
1170 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1177 else if( (ctl_line[0] == '#')
1178 || (ctl_line[0] == ' ')
1179 || (ctl_line[0] == '\n'))
1180 ; /* ignore these lines */
1182 printf("Command not understood (%s)\n", ctl_line);
1188 build_stream_name(int type, const gchar* dir) {
1190 State *s = &uzbl.state;
1193 xwin_str = itos((int)uzbl.xwin);
1195 str = g_strdup_printf
1196 ("%s/uzbl_fifo_%s", dir,
1197 s->instance_name ? s->instance_name : xwin_str);
1198 } else if (type == SOCKET) {
1199 str = g_strdup_printf
1200 ("%s/uzbl_socket_%s", dir,
1201 s->instance_name ? s->instance_name : xwin_str );
1208 control_fifo(GIOChannel *gio, GIOCondition condition) {
1209 if (uzbl.state.verbose)
1210 printf("triggered\n");
1215 if (condition & G_IO_HUP)
1216 g_error ("Fifo: Read end of pipe died!\n");
1219 g_error ("Fifo: GIOChannel broke\n");
1221 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1222 if (ret == G_IO_STATUS_ERROR) {
1223 g_error ("Fifo: Error reading: %s\n", err->message);
1227 parse_cmd_line(ctl_line);
1234 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1235 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1236 if (unlink(uzbl.comm.fifo_path) == -1)
1237 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1238 g_free(uzbl.comm.fifo_path);
1239 uzbl.comm.fifo_path = NULL;
1242 if (*dir == ' ') { /* space unsets the variable */
1246 GIOChannel *chan = NULL;
1247 GError *error = NULL;
1248 gchar *path = build_stream_name(FIFO, dir);
1250 if (!file_exists(path)) {
1251 if (mkfifo (path, 0666) == 0) {
1252 // 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.
1253 chan = g_io_channel_new_file(path, "r+", &error);
1255 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1256 if (uzbl.state.verbose)
1257 printf ("init_fifo: created successfully as %s\n", path);
1258 uzbl.comm.fifo_path = path;
1260 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1261 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1262 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1263 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1265 /* if we got this far, there was an error; cleanup */
1266 if (error) g_error_free (error);
1272 control_stdin(GIOChannel *gio, GIOCondition condition) {
1274 gchar *ctl_line = NULL;
1277 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1278 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1281 parse_cmd_line(ctl_line);
1289 GIOChannel *chan = NULL;
1290 GError *error = NULL;
1292 chan = g_io_channel_unix_new(fileno(stdin));
1294 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1295 g_error ("Stdin: could not add watch\n");
1297 if (uzbl.state.verbose)
1298 printf ("Stdin: watch added successfully\n");
1301 g_error ("Stdin: Error while opening: %s\n", error->message);
1303 if (error) g_error_free (error);
1307 control_socket(GIOChannel *chan) {
1308 struct sockaddr_un remote;
1309 char buffer[512], *ctl_line;
1311 int sock, clientsock, n, done;
1314 sock = g_io_channel_unix_get_fd(chan);
1316 memset (buffer, 0, sizeof (buffer));
1318 t = sizeof (remote);
1319 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1323 memset (temp, 0, sizeof (temp));
1324 n = recv (clientsock, temp, 128, 0);
1326 buffer[strlen (buffer)] = '\0';
1330 strcat (buffer, temp);
1333 if (strcmp (buffer, "\n") < 0) {
1334 buffer[strlen (buffer) - 1] = '\0';
1336 buffer[strlen (buffer)] = '\0';
1339 ctl_line = g_strdup(buffer);
1340 parse_cmd_line (ctl_line);
1343 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1344 GError *error = NULL;
1347 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1348 if (ret == G_IO_STATUS_ERROR)
1349 g_error ("Error reading: %s\n", error->message);
1351 printf("Got line %s (%u bytes) \n",ctl_line, len);
1353 parse_line(ctl_line);
1361 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1362 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1363 if (unlink(uzbl.comm.socket_path) == -1)
1364 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1365 g_free(uzbl.comm.socket_path);
1366 uzbl.comm.socket_path = NULL;
1374 GIOChannel *chan = NULL;
1376 struct sockaddr_un local;
1377 gchar *path = build_stream_name(SOCKET, dir);
1379 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1381 local.sun_family = AF_UNIX;
1382 strcpy (local.sun_path, path);
1383 unlink (local.sun_path);
1385 len = strlen (local.sun_path) + sizeof (local.sun_family);
1386 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1387 if (uzbl.state.verbose)
1388 printf ("init_socket: opened in %s\n", path);
1391 if( (chan = g_io_channel_unix_new(sock)) ) {
1392 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1393 uzbl.comm.socket_path = path;
1396 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1398 /* if we got this far, there was an error; cleanup */
1405 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1406 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1408 // this function may be called very early when the templates are not set (yet), hence the checks
1410 update_title (void) {
1411 Behaviour *b = &uzbl.behave;
1414 if (b->show_status) {
1415 if (b->title_format_short) {
1416 parsed = expand_template(b->title_format_short);
1417 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1420 if (b->status_format) {
1421 parsed = expand_template(b->status_format);
1422 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1425 if (b->status_background) {
1427 gdk_color_parse (b->status_background, &color);
1428 //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)
1429 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1432 if (b->title_format_long) {
1433 parsed = expand_template(b->title_format_long);
1434 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1441 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1443 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1447 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1448 || 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)
1451 /* turn off insert mode (if always_insert_mode is not used) */
1452 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1453 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1458 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1461 if (event->keyval == GDK_Escape) {
1462 g_string_truncate(uzbl.state.keycmd, 0);
1467 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1468 if (event->keyval == GDK_Insert) {
1470 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1471 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1473 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1476 g_string_append (uzbl.state.keycmd, str);
1483 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1484 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1488 gboolean key_ret = FALSE;
1489 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1491 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1493 run_keycmd(key_ret);
1495 if (key_ret) return (!uzbl.behave.insert_mode);
1500 run_keycmd(const gboolean key_ret) {
1501 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1503 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1504 g_string_truncate(uzbl.state.keycmd, 0);
1505 parse_command(action->name, action->param);
1509 /* try if it's an incremental keycmd or one that takes args, and run it */
1510 GString* short_keys = g_string_new ("");
1511 GString* short_keys_inc = g_string_new ("");
1513 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1514 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1515 g_string_assign(short_keys_inc, short_keys->str);
1516 g_string_append_c(short_keys, '_');
1517 g_string_append_c(short_keys_inc, '*');
1519 gboolean exec_now = FALSE;
1520 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1521 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1522 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1523 if (key_ret) { /* just quit the incremental command on return */
1524 g_string_truncate(uzbl.state.keycmd, 0);
1526 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1530 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1531 GString* actionname = g_string_new ("");
1532 GString* actionparam = g_string_new ("");
1533 g_string_erase (parampart, 0, i+1);
1535 g_string_printf (actionname, action->name, parampart->str);
1537 g_string_printf (actionparam, action->param, parampart->str);
1538 parse_command(actionname->str, actionparam->str);
1539 g_string_free (actionname, TRUE);
1540 g_string_free (actionparam, TRUE);
1541 g_string_free (parampart, TRUE);
1543 g_string_truncate(uzbl.state.keycmd, 0);
1547 g_string_truncate(short_keys, short_keys->len - 1);
1549 g_string_free (short_keys, TRUE);
1550 g_string_free (short_keys_inc, TRUE);
1557 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1558 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
1560 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1561 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1563 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1564 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1565 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1566 g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1567 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1568 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1569 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1570 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1571 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1572 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1573 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1575 return scrolled_window;
1582 g->mainbar = gtk_hbox_new (FALSE, 0);
1584 g->mainbar_label = gtk_label_new ("");
1585 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1586 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1587 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1588 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1589 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1594 GtkWidget* create_window () {
1595 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1596 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1597 gtk_widget_set_name (window, "Uzbl browser");
1598 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1604 run_handler (const gchar *act, const gchar *args) {
1605 char **parts = g_strsplit(act, " ", 2);
1607 else if ((g_strcmp0(parts[0], "spawn") == 0)
1608 || (g_strcmp0(parts[0], "sh") == 0)) {
1610 GString *a = g_string_new ("");
1612 spawnparts = split_quoted(parts[1], FALSE);
1613 g_string_append_printf(a, "%s", spawnparts[0]);
1614 if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1615 for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1616 g_string_append_printf(a, " %s", spawnparts[i]);
1617 parse_command(parts[0], a->str);
1618 g_string_free (a, TRUE);
1619 g_strfreev (spawnparts);
1621 parse_command(parts[0], parts[1]);
1626 add_binding (const gchar *key, const gchar *act) {
1627 char **parts = g_strsplit(act, " ", 2);
1634 if (uzbl.state.verbose)
1635 printf ("Binding %-10s : %s\n", key, act);
1637 action = new_action(parts[0], parts[1]);
1638 g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
1644 get_xdg_var (XDG_Var xdg) {
1645 const gchar* actual_value = getenv (xdg.environmental);
1646 const gchar* home = getenv ("HOME");
1648 gchar* return_value = str_replace ("~", home, actual_value);
1650 if (! actual_value || strcmp (actual_value, "") == 0) {
1651 if (xdg.default_value) {
1652 return_value = str_replace ("~", home, xdg.default_value);
1654 return_value = NULL;
1657 return return_value;
1661 find_xdg_file (int xdg_type, char* filename) {
1662 /* xdg_type = 0 => config
1663 xdg_type = 1 => data
1664 xdg_type = 2 => cache*/
1666 gchar* temporary_file = malloc (1024);
1667 gchar* temporary_string = NULL;
1671 buf = get_xdg_var (XDG[xdg_type]);
1672 strcpy (temporary_file, buf);
1673 strcat (temporary_file, filename);
1676 if (! file_exists (temporary_file) && xdg_type != 2) {
1677 buf = get_xdg_var (XDG[3 + xdg_type]);
1678 temporary_string = (char *) strtok_r (buf, ":", &saveptr);
1681 while (temporary_string && ! file_exists (temporary_file)) {
1682 strcpy (temporary_file, temporary_string);
1683 strcat (temporary_file, filename);
1684 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1688 if (file_exists (temporary_file)) {
1689 return temporary_file;
1697 State *s = &uzbl.state;
1698 Network *n = &uzbl.net;
1700 uzbl.behave.reset_command_mode = 1;
1702 if (!s->config_file) {
1703 s->config_file = find_xdg_file (0, "/uzbl/config");
1706 if (s->config_file) {
1707 GIOChannel *chan = NULL;
1708 gchar *readbuf = NULL;
1711 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1714 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1715 == G_IO_STATUS_NORMAL) {
1716 parse_cmd_line(readbuf);
1720 g_io_channel_unref (chan);
1721 if (uzbl.state.verbose)
1722 printf ("Config %s loaded\n", s->config_file);
1724 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1727 if (uzbl.state.verbose)
1728 printf ("No configuration file loaded.\n");
1730 if (!uzbl.behave.status_format)
1731 set_var_value("status_format", STATUS_DEFAULT);
1732 if (!uzbl.behave.title_format_long)
1733 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1734 if (!uzbl.behave.title_format_short)
1735 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1738 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1742 set_useragent(gchar *val) {
1747 gchar *ua = expand_template(val);
1749 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1753 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1756 if (!uzbl.behave.cookie_handler) return;
1758 gchar * stdout = NULL;
1759 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1760 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1761 gchar *action = g_strdup ("GET");
1762 SoupURI * soup_uri = soup_message_get_uri(msg);
1763 sharg_append(a, action);
1764 sharg_append(a, soup_uri->host);
1765 sharg_append(a, soup_uri->path);
1766 run_command(uzbl.behave.cookie_handler, 0, a->data, TRUE, &stdout); /* TODO: use handler */
1767 //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */
1769 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1772 g_array_free(a, TRUE);
1776 save_cookies (SoupMessage *msg, gpointer user_data){
1780 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1781 cookie = soup_cookie_to_set_cookie_header(ck->data);
1782 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1783 SoupURI * soup_uri = soup_message_get_uri(msg);
1784 gchar *action = strdup("PUT");
1785 sharg_append(a, action);
1786 sharg_append(a, soup_uri->host);
1787 sharg_append(a, soup_uri->path);
1788 sharg_append(a, cookie);
1789 run_command(uzbl.behave.cookie_handler, 0, a->data, FALSE, NULL);
1792 g_array_free(a, TRUE);
1799 main (int argc, char* argv[]) {
1800 gtk_init (&argc, &argv);
1801 if (!g_thread_supported ())
1802 g_thread_init (NULL);
1804 uzbl.state.executable_path = g_strdup(argv[0]);
1805 uzbl.state.selected_url = NULL;
1806 uzbl.state.searchtx = NULL;
1808 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1809 g_option_context_add_main_entries (context, entries, NULL);
1810 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1811 g_option_context_parse (context, &argc, &argv, NULL);
1812 g_option_context_free(context);
1813 /* initialize hash table */
1814 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1816 uzbl.net.soup_session = webkit_get_default_session();
1817 uzbl.state.keycmd = g_string_new("");
1819 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1820 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1821 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1822 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1824 if(uname(&uzbl.state.unameinfo) == -1)
1825 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1830 make_var_to_name_hash();
1833 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1835 uzbl.gui.scrolled_win = create_browser();
1838 /* initial packing */
1839 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1840 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1842 uzbl.gui.main_window = create_window ();
1843 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1846 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1847 gtk_widget_show_all (uzbl.gui.main_window);
1848 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1850 if (uzbl.state.verbose) {
1851 printf("Uzbl start location: %s\n", argv[0]);
1852 printf("window_id %i\n",(int) uzbl.xwin);
1853 printf("pid %i\n", getpid ());
1854 printf("name: %s\n", uzbl.state.instance_name);
1857 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1858 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1859 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1860 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1861 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1865 if (!uzbl.behave.show_status)
1866 gtk_widget_hide(uzbl.gui.mainbar);
1872 //if(uzbl.state.uri)
1873 // load_uri (uzbl.gui.web_view, uzbl.state.uri);
1879 return EXIT_SUCCESS;
1882 /* vi: set et ts=4: */