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*, const char *);
142 /* --- UTILITY FUNCTIONS --- */
148 snprintf(tmp, sizeof(tmp), "%i", val);
149 return g_strdup(tmp);
153 str_replace (const char* search, const char* replace, const char* string) {
157 buf = g_strsplit (string, search, -1);
158 ret = g_strjoinv (replace, buf);
165 read_file_by_line (gchar *path) {
166 GIOChannel *chan = NULL;
167 gchar *readbuf = NULL;
172 chan = g_io_channel_new_file(path, "r", NULL);
175 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
176 == G_IO_STATUS_NORMAL) {
177 lines[i] = g_strdup (readbuf);
182 g_io_channel_unref (chan);
184 fprintf(stderr, "File '%s' not be read.\n", path);
192 gchar* parseenv (const char* string) {
193 extern char** environ;
194 gchar* newstring = g_strdup (string);
197 while (environ[i] != NULL) {
198 gchar** env = g_strsplit (environ[i], "=", 0);
199 gchar* envname = malloc (strlen (env[0]) + 1);
201 strcat (envname, "$");
202 strcat (envname, env[0]);
204 newstring = str_replace(envname, env[1], newstring);
207 //g_strfreev (env); - This still breaks uzbl, but shouldn't. The mystery thickens...
215 setup_signal(int signr, sigfunc *shandler) {
216 struct sigaction nh, oh;
218 nh.sa_handler = shandler;
219 sigemptyset(&nh.sa_mask);
222 if(sigaction(signr, &nh, &oh) < 0)
230 if (uzbl.behave.fifo_dir)
231 unlink (uzbl.comm.fifo_path);
232 if (uzbl.behave.socket_dir)
233 unlink (uzbl.comm.socket_path);
235 g_free(uzbl.state.executable_path);
236 g_string_free(uzbl.state.keycmd, TRUE);
237 g_hash_table_destroy(uzbl.bindings);
238 g_hash_table_destroy(uzbl.behave.commands);
242 /* --- SIGNAL HANDLER --- */
245 catch_sigterm(int s) {
251 catch_sigint(int s) {
257 /* --- CALLBACKS --- */
260 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
263 (void) navigation_action;
264 (void) policy_decision;
266 const gchar* uri = webkit_network_request_get_uri (request);
267 if (uzbl.state.verbose)
268 printf("New window requested -> %s \n", uri);
269 new_window_load_uri(uri);
274 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
278 if (uzbl.state.selected_url != NULL) {
279 if (uzbl.state.verbose)
280 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
281 new_window_load_uri(uzbl.state.selected_url);
283 if (uzbl.state.verbose)
284 printf("New web view -> %s\n","Nothing to open, exiting");
290 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
293 if (uzbl.behave.download_handler) {
294 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
295 if (uzbl.state.verbose)
296 printf("Download -> %s\n",uri);
297 /* if urls not escaped, we may have to escape and quote uri before this call */
298 run_handler(uzbl.behave.download_handler, uri);
303 /* scroll a bar in a given direction */
305 scroll (GtkAdjustment* bar, const char *param) {
309 amount = g_ascii_strtod(param, &end);
310 if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
311 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
314 static void scroll_begin(WebKitWebView* page, const char *param) {
315 (void) page; (void) param;
316 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
319 static void scroll_end(WebKitWebView* page, const char *param) {
320 (void) page; (void) param;
321 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
322 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
325 static void scroll_vert(WebKitWebView* page, const char *param) {
327 scroll(uzbl.gui.bar_v, param);
330 static void scroll_horz(WebKitWebView* page, const char *param) {
332 scroll(uzbl.gui.bar_h, param);
337 if (!uzbl.behave.show_status) {
338 gtk_widget_hide(uzbl.gui.mainbar);
340 gtk_widget_show(uzbl.gui.mainbar);
346 toggle_status_cb (WebKitWebView* page, const char *param) {
350 if (uzbl.behave.show_status) {
351 gtk_widget_hide(uzbl.gui.mainbar);
353 gtk_widget_show(uzbl.gui.mainbar);
355 uzbl.behave.show_status = !uzbl.behave.show_status;
360 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
364 //Set selected_url state variable
365 g_free(uzbl.state.selected_url);
366 uzbl.state.selected_url = NULL;
368 uzbl.state.selected_url = g_strdup(link);
374 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
378 if (uzbl.gui.main_title)
379 g_free (uzbl.gui.main_title);
380 uzbl.gui.main_title = g_strdup (title);
385 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
388 uzbl.gui.sbar.load_progress = progress;
393 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
397 if (uzbl.behave.load_finish_handler)
398 run_handler(uzbl.behave.load_finish_handler, "");
402 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
406 if (uzbl.behave.load_start_handler)
407 run_handler(uzbl.behave.load_start_handler, "");
411 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
414 free (uzbl.state.uri);
415 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
416 uzbl.state.uri = g_string_free (newuri, FALSE);
417 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
418 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
421 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
422 if (uzbl.behave.load_commit_handler)
423 run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
427 destroy_cb (GtkWidget* widget, gpointer data) {
435 if (uzbl.behave.history_handler) {
437 struct tm * timeinfo;
440 timeinfo = localtime ( &rawtime );
441 strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
442 run_handler(uzbl.behave.history_handler, date);
447 /* VIEW funcs (little webkit wrappers) */
448 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, const char *param){(void)param; webkit_web_view_##name(page);}
450 VIEWFUNC(reload_bypass_cache)
451 VIEWFUNC(stop_loading)
458 /* -- command to callback/function map for things we cannot attach to any signals */
461 static struct {char *name; Command command;} cmdlist[] =
463 { "back", view_go_back },
464 { "forward", view_go_forward },
465 { "scroll_vert", scroll_vert },
466 { "scroll_horz", scroll_horz },
467 { "scroll_begin", scroll_begin },
468 { "scroll_end", scroll_end },
469 { "reload", view_reload, },
470 { "reload_ign_cache", view_reload_bypass_cache},
471 { "stop", view_stop_loading, },
472 { "zoom_in", view_zoom_in, }, //Can crash (when max zoom reached?).
473 { "zoom_out", view_zoom_out, },
476 { "script", run_external_js },
477 { "toggle_status", toggle_status_cb },
480 { "exit", close_uzbl },
481 { "search", search_forward_text },
482 { "search_reverse", search_reverse_text },
483 { "toggle_insert_mode", toggle_insert_mode },
491 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
493 for (i = 0; i < LENGTH(cmdlist); i++)
494 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
497 /* -- CORE FUNCTIONS -- */
500 free_action(gpointer act) {
501 Action *action = (Action*)act;
502 g_free(action->name);
504 g_free(action->param);
509 new_action(const gchar *name, const gchar *param) {
510 Action *action = g_new(Action, 1);
512 action->name = g_strdup(name);
514 action->param = g_strdup(param);
516 action->param = NULL;
522 file_exists (const char * filename) {
523 return (access(filename, F_OK) == 0);
527 toggle_insert_mode(WebKitWebView *page, const gchar *param) {
532 if (strcmp (param, "0") == 0) {
533 uzbl.behave.insert_mode = FALSE;
535 uzbl.behave.insert_mode = TRUE;
538 uzbl.behave.insert_mode = ! uzbl.behave.insert_mode;
545 load_uri (WebKitWebView * web_view, const gchar *param) {
547 GString* newuri = g_string_new (param);
548 if (g_strrstr (param, "://") == NULL)
549 g_string_prepend (newuri, "http://");
550 /* if we do handle cookies, ask our handler for them */
551 webkit_web_view_load_uri (web_view, newuri->str);
552 g_string_free (newuri, TRUE);
557 run_js (WebKitWebView * web_view, const gchar *param) {
559 webkit_web_view_execute_script (web_view, param);
563 run_external_js (WebKitWebView * web_view, const gchar *param) {
565 gchar** splitted = g_strsplit (param, " ", 2);
566 gchar** lines = read_file_by_line (splitted[0]);
570 if (lines[0] != NULL) {
571 for (i = 0; lines[i] != NULL; i ++) {
573 js = g_strdup (lines[i]);
575 gchar* newjs = g_strconcat (js, lines[i], NULL);
578 //g_free (lines[i]); - Another mysterious breakage
581 if (uzbl.state.verbose)
582 printf ("External JavaScript file %s loaded\n", splitted[0]);
585 gchar* newjs = str_replace("%s", splitted[1], js);
588 webkit_web_view_execute_script (web_view, js);
591 fprintf(stderr, "JavaScript file '%s' not be read.\n", splitted[0]);
597 search_text (WebKitWebView *page, const char *param, const gboolean forward) {
598 if ((param) && (param[0] != '\0')) {
599 uzbl.state.searchtx = g_strdup(param);
601 if (uzbl.state.searchtx != NULL) {
602 if (uzbl.state.verbose)
603 printf ("Searching: %s\n", uzbl.state.searchtx);
605 if (g_strcmp0 (uzbl.state.searchtx, uzbl.state.searchold) != 0) {
606 webkit_web_view_unmark_text_matches (page);
607 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
609 if (uzbl.state.searchold != NULL)
610 g_free (uzbl.state.searchold);
612 uzbl.state.searchold = g_strdup (uzbl.state.searchtx);
615 webkit_web_view_set_highlight_text_matches (page, TRUE);
616 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
617 g_free(uzbl.state.searchtx);
618 uzbl.state.searchtx = NULL;
623 search_forward_text (WebKitWebView *page, const char *param) {
624 search_text(page, param, TRUE);
628 search_reverse_text (WebKitWebView *page, const char *param) {
629 search_text(page, param, FALSE);
633 new_window_load_uri (const gchar * uri) {
634 GString* to_execute = g_string_new ("");
635 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
637 for (i = 0; entries[i].long_name != NULL; i++) {
638 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
639 gchar** str = (gchar**)entries[i].arg_data;
641 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
645 if (uzbl.state.verbose)
646 printf("\n%s\n", to_execute->str);
647 g_spawn_command_line_async (to_execute->str, NULL);
648 g_string_free (to_execute, TRUE);
652 close_uzbl (WebKitWebView *page, const char *param) {
658 /* --Statusbar functions-- */
660 build_progressbar_ascii(int percent) {
664 GString *bar = g_string_new("");
666 l = (double)percent*((double)width/100.);
667 l = (int)(l+.5)>=(int)l ? l+.5 : l;
669 for(i=0; i<(int)l; i++)
670 g_string_append(bar, "=");
673 g_string_append(bar, "·");
675 return g_string_free(bar, FALSE);
680 const GScannerConfig scan_config = {
683 ) /* cset_skip_characters */,
688 ) /* cset_identifier_first */,
695 ) /* cset_identifier_nth */,
696 ( "" ) /* cpair_comment_single */,
698 TRUE /* case_sensitive */,
700 FALSE /* skip_comment_multi */,
701 FALSE /* skip_comment_single */,
702 FALSE /* scan_comment_multi */,
703 TRUE /* scan_identifier */,
704 TRUE /* scan_identifier_1char */,
705 FALSE /* scan_identifier_NULL */,
706 TRUE /* scan_symbols */,
707 FALSE /* scan_binary */,
708 FALSE /* scan_octal */,
709 FALSE /* scan_float */,
710 FALSE /* scan_hex */,
711 FALSE /* scan_hex_dollar */,
712 FALSE /* scan_string_sq */,
713 FALSE /* scan_string_dq */,
714 TRUE /* numbers_2_int */,
715 FALSE /* int_2_float */,
716 FALSE /* identifier_2_string */,
717 FALSE /* char_2_token */,
718 FALSE /* symbol_2_token */,
719 TRUE /* scope_0_fallback */,
724 uzbl.scan = g_scanner_new(&scan_config);
725 while(symp->symbol_name) {
726 g_scanner_scope_add_symbol(uzbl.scan, 0,
728 GINT_TO_POINTER(symp->symbol_token));
734 expand_template(const char *template) {
735 if(!template) return NULL;
737 GTokenType token = G_TOKEN_NONE;
738 GString *ret = g_string_new("");
742 g_scanner_input_text(uzbl.scan, template, strlen(template));
743 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
744 token = g_scanner_get_next_token(uzbl.scan);
746 if(token == G_TOKEN_SYMBOL) {
747 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
750 buf = uzbl.state.uri?
751 g_markup_printf_escaped("%s", uzbl.state.uri) :
753 g_string_append(ret, buf);
757 buf = itos(uzbl.gui.sbar.load_progress);
758 g_string_append(ret, buf);
761 case SYM_LOADPRGSBAR:
762 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
763 g_string_append(ret, buf);
767 buf = uzbl.gui.main_title?
768 g_markup_printf_escaped("%s", uzbl.gui.main_title) :
770 g_string_append(ret, buf);
773 case SYM_SELECTED_URI:
774 buf = uzbl.state.selected_url?
775 g_markup_printf_escaped("%s", uzbl.state.selected_url) :
777 g_string_append(ret, buf);
781 buf = itos(uzbl.xwin);
783 uzbl.state.instance_name?uzbl.state.instance_name:buf);
787 buf = uzbl.state.keycmd->str?
788 g_markup_printf_escaped("%s", uzbl.state.keycmd->str) :
790 g_string_append(ret, buf);
795 uzbl.behave.insert_mode?"[I]":"[C]");
799 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
803 buf = itos(WEBKIT_MAJOR_VERSION);
804 g_string_append(ret, buf);
808 buf = itos(WEBKIT_MINOR_VERSION);
809 g_string_append(ret, buf);
813 buf = itos(WEBKIT_MICRO_VERSION);
814 g_string_append(ret, buf);
818 g_string_append(ret, uzbl.state.unameinfo.sysname);
821 g_string_append(ret, uzbl.state.unameinfo.nodename);
824 g_string_append(ret, uzbl.state.unameinfo.release);
827 g_string_append(ret, uzbl.state.unameinfo.version);
830 g_string_append(ret, uzbl.state.unameinfo.machine);
833 g_string_append(ret, ARCH);
837 g_string_append(ret, uzbl.state.unameinfo.domainname);
841 g_string_append(ret, COMMIT);
847 else if(token == G_TOKEN_INT) {
848 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
849 g_string_append(ret, buf);
852 else if(token == G_TOKEN_IDENTIFIER) {
853 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
855 else if(token == G_TOKEN_CHAR) {
856 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
860 return g_string_free(ret, FALSE);
862 /* --End Statusbar functions-- */
865 sharg_append(GArray *a, const gchar *str) {
866 const gchar *s = (str ? str : "");
867 g_array_append_val(a, s);
870 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
872 run_command (const gchar *command, const guint npre, const gchar **args,
873 const gboolean sync, char **stdout) {
874 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
877 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
878 gchar *pid = itos(getpid());
879 gchar *xwin = itos(uzbl.xwin);
881 sharg_append(a, command);
882 for (i = 0; i < npre; i++) /* add n args before the default vars */
883 sharg_append(a, args[i]);
884 sharg_append(a, uzbl.state.config_file);
885 sharg_append(a, pid);
886 sharg_append(a, xwin);
887 sharg_append(a, uzbl.comm.fifo_path);
888 sharg_append(a, uzbl.comm.socket_path);
889 sharg_append(a, uzbl.state.uri);
890 sharg_append(a, uzbl.gui.main_title);
892 for (i = npre; i < g_strv_length((gchar**)args); i++)
893 sharg_append(a, args[i]);
895 if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
896 NULL, NULL, stdout, NULL, NULL, &err);
897 else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
898 NULL, NULL, NULL, &err);
900 if (uzbl.state.verbose) {
901 GString *s = g_string_new("spawned:");
902 for (i = 0; i < (a->len); i++) {
903 gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
904 g_string_append_printf(s, " %s", qarg);
907 g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
908 printf("%s\n", s->str);
909 g_string_free(s, TRUE);
912 g_printerr("error on run_command: %s\n", err->message);
917 g_array_free (a, TRUE);
922 split_quoted(const gchar* src, const gboolean unquote) {
923 /* split on unquoted space, return array of strings;
924 remove a layer of quotes and backslashes if unquote */
927 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
928 GString *s = g_string_new ("");
932 for (p = src; *p != '\0'; p++) {
933 if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
934 else if (*p == '\\') { g_string_append_c(s, *p++);
935 g_string_append_c(s, *p); }
936 else if ((*p == '"') && unquote && !sq) dq = !dq;
937 else if (*p == '"' && !sq) { g_string_append_c(s, *p);
939 else if ((*p == '\'') && unquote && !dq) sq = !sq;
940 else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
942 else if ((*p == ' ') && !dq && !sq) {
943 dup = g_strdup(s->str);
944 g_array_append_val(a, dup);
945 g_string_truncate(s, 0);
946 } else g_string_append_c(s, *p);
948 dup = g_strdup(s->str);
949 g_array_append_val(a, dup);
950 ret = (gchar**)a->data;
951 g_array_free (a, FALSE);
952 g_string_free (s, FALSE);
957 spawn(WebKitWebView *web_view, const char *param) {
959 //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
960 gchar **cmd = split_quoted(param, TRUE);
961 if (cmd) run_command(cmd[0], 0, &cmd[1], FALSE, NULL);
962 g_strfreev ((gchar**)cmd);
966 spawn_sh(WebKitWebView *web_view, const char *param) {
968 if (!uzbl.behave.shell_cmd) {
969 g_printerr ("spawn_sh: shell_cmd is not set!\n");
974 gchar *spacer = g_strdup("");
975 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
976 gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
977 gchar **p = split_quoted(param, TRUE);
978 for (i = 1; i < g_strv_length(cmd); i++)
979 sharg_append(a, cmd[i]);
980 sharg_append(a, p[0]); /* the first param comes right after shell_cmd;
981 the rest come after default args */
982 sharg_append(a, spacer);
983 for (i = 1; i < g_strv_length(p); i++)
984 sharg_append(a, p[i]);
985 if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, a->data, FALSE, NULL);
989 g_array_free (a, FALSE);
993 parse_command(const char *cmd, const char *param) {
996 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd)))
997 c(uzbl.gui.web_view, param);
999 fprintf (stderr, "command \"%s\" not understood. ignoring.\n", cmd);
1002 /* command parser */
1005 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
1006 G_REGEX_OPTIMIZE, 0, NULL);
1007 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
1008 G_REGEX_OPTIMIZE, 0, NULL);
1009 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
1010 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
1011 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
1012 G_REGEX_OPTIMIZE, 0, NULL);
1013 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
1014 G_REGEX_OPTIMIZE, 0, NULL);
1018 get_var_value(gchar *name) {
1021 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1022 if(var_is("uri", name)
1023 || var_is("status_message", name)
1024 || var_is("status_format", name)
1025 || var_is("status_background", name)
1026 || var_is("title_format_short", name)
1027 || var_is("title_format_long", name)
1028 || var_is("modkey", name)
1029 || var_is("load_finish_handler", name)
1030 || var_is("load_start_handler", name)
1031 || var_is("load_commit_handler", name)
1032 || var_is("history_handler", name)
1033 || var_is("download_handler", name)
1034 || var_is("cookie_handler", name)
1035 || var_is("fifo_dir", name)
1036 || var_is("socket_dir", name)
1037 || var_is("shell_cmd", name)
1038 || var_is("proxy_url", name)
1039 || var_is("useragent", name))
1041 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
1042 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
1051 if(*uzbl.net.proxy_url == ' '
1052 || uzbl.net.proxy_url == NULL) {
1053 soup_session_remove_feature_by_type(uzbl.net.soup_session,
1054 (GType) SOUP_SESSION_PROXY_URI);
1057 suri = soup_uri_new(uzbl.net.proxy_url);
1058 g_object_set(G_OBJECT(uzbl.net.soup_session),
1059 SOUP_SESSION_PROXY_URI,
1061 soup_uri_free(suri);
1069 gtk_widget_ref(uzbl.gui.scrolled_win);
1070 gtk_widget_ref(uzbl.gui.mainbar);
1071 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1072 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1074 if(uzbl.behave.status_top) {
1075 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1076 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1079 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1080 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1082 gtk_widget_unref(uzbl.gui.scrolled_win);
1083 gtk_widget_unref(uzbl.gui.mainbar);
1084 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1088 var_is(const char *x, const char *y) {
1089 return (strcmp(x, y) == 0 ? TRUE : FALSE );
1093 set_var_value(gchar *name, gchar *val) {
1098 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1099 if(var_is("status_message", name)
1100 || var_is("status_background", name)
1101 || var_is("status_format", name)
1102 || var_is("title_format_long", name)
1103 || var_is("title_format_short", name)
1104 || var_is("load_finish_handler", name)
1105 || var_is("load_start_handler", name)
1106 || var_is("load_commit_handler", name)
1107 || var_is("history_handler", name)
1108 || var_is("download_handler", name)
1109 || var_is("cookie_handler", name)) {
1115 else if(var_is("uri", name)) {
1118 load_uri(uzbl.gui.web_view, (const gchar*)*p);
1120 else if(var_is("proxy_url", name)) {
1125 else if(var_is("fifo_dir", name)) {
1127 buf = init_fifo(val);
1128 *p = buf?buf:g_strdup("");
1130 else if(var_is("socket_dir", name)) {
1132 buf = init_socket(val);
1133 *p = buf?buf:g_strdup("");
1135 else if(var_is("modkey", name)) {
1138 *p = g_utf8_strup(val, -1);
1139 uzbl.behave.modmask = 0;
1140 for (i = 0; modkeys[i].key != NULL; i++) {
1141 if (g_strrstr(*p, modkeys[i].key))
1142 uzbl.behave.modmask |= modkeys[i].mask;
1145 else if(var_is("useragent", name)) {
1147 buf = set_useragent(val);
1148 *p = buf?buf:g_strdup("");
1150 else if(var_is("shell_cmd", name)) {
1154 /* variables that take int values */
1157 *ip = (int)strtoul(val, &endp, 10);
1159 if(var_is("show_status", name)) {
1162 else if(var_is("always_insert_mode", name)) {
1163 uzbl.behave.insert_mode =
1164 uzbl.behave.always_insert_mode ? TRUE : FALSE;
1167 else if (var_is("max_conns", name)) {
1168 g_object_set(G_OBJECT(uzbl.net.soup_session),
1169 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1171 else if (var_is("max_conns_host", name)) {
1172 g_object_set(G_OBJECT(uzbl.net.soup_session),
1173 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1175 else if (var_is("http_debug", name)) {
1176 soup_session_remove_feature
1177 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1178 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1179 /*g_free(uzbl.net.soup_logger);*/
1181 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1182 soup_session_add_feature(uzbl.net.soup_session,
1183 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1185 else if (var_is("status_top", name)) {
1188 else if (var_is("default_font_size", name)) {
1189 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1190 g_object_set (G_OBJECT(ws), "default-font-size", *ip, NULL);
1192 else if (var_is("minimum_font_size", name)) {
1193 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1194 g_object_set (G_OBJECT(ws), "minimum-font-size", *ip, NULL);
1202 runcmd(WebKitWebView* page, const char *param) {
1204 parse_cmd_line(param);
1208 parse_cmd_line(const char *ctl_line) {
1212 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1213 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1214 if(tokens[0][0] == 0) {
1215 gchar* value = parseenv (tokens[2]);
1216 set_var_value(tokens[1], value);
1221 printf("Error in command: %s\n", tokens[0]);
1224 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1225 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1226 if(tokens[0][0] == 0) {
1227 get_var_value(tokens[1]);
1231 printf("Error in command: %s\n", tokens[0]);
1234 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1235 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1236 if(tokens[0][0] == 0) {
1237 gchar* value = parseenv (tokens[2]);
1238 add_binding(tokens[1], value);
1243 printf("Error in command: %s\n", tokens[0]);
1246 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1247 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1248 if(tokens[0][0] == 0) {
1249 parse_command(tokens[1], tokens[2]);
1253 printf("Error in command: %s\n", tokens[0]);
1255 /* KEYCMD command */
1256 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1257 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1258 if(tokens[0][0] == 0) {
1259 /* should incremental commands want each individual "keystroke"
1260 sent in a loop or the whole string in one go like now? */
1261 g_string_assign(uzbl.state.keycmd, tokens[1]);
1263 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1270 else if( (ctl_line[0] == '#')
1271 || (ctl_line[0] == ' ')
1272 || (ctl_line[0] == '\n'))
1273 ; /* ignore these lines */
1275 printf("Command not understood (%s)\n", ctl_line);
1281 build_stream_name(int type, const gchar* dir) {
1283 State *s = &uzbl.state;
1286 xwin_str = itos((int)uzbl.xwin);
1288 str = g_strdup_printf
1289 ("%s/uzbl_fifo_%s", dir,
1290 s->instance_name ? s->instance_name : xwin_str);
1291 } else if (type == SOCKET) {
1292 str = g_strdup_printf
1293 ("%s/uzbl_socket_%s", dir,
1294 s->instance_name ? s->instance_name : xwin_str );
1301 control_fifo(GIOChannel *gio, GIOCondition condition) {
1302 if (uzbl.state.verbose)
1303 printf("triggered\n");
1308 if (condition & G_IO_HUP)
1309 g_error ("Fifo: Read end of pipe died!\n");
1312 g_error ("Fifo: GIOChannel broke\n");
1314 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1315 if (ret == G_IO_STATUS_ERROR) {
1316 g_error ("Fifo: Error reading: %s\n", err->message);
1320 parse_cmd_line(ctl_line);
1327 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1328 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1329 if (unlink(uzbl.comm.fifo_path) == -1)
1330 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1331 g_free(uzbl.comm.fifo_path);
1332 uzbl.comm.fifo_path = NULL;
1335 if (*dir == ' ') { /* space unsets the variable */
1339 GIOChannel *chan = NULL;
1340 GError *error = NULL;
1341 gchar *path = build_stream_name(FIFO, dir);
1343 if (!file_exists(path)) {
1344 if (mkfifo (path, 0666) == 0) {
1345 // 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.
1346 chan = g_io_channel_new_file(path, "r+", &error);
1348 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1349 if (uzbl.state.verbose)
1350 printf ("init_fifo: created successfully as %s\n", path);
1351 uzbl.comm.fifo_path = path;
1353 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1354 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1355 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1356 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1358 /* if we got this far, there was an error; cleanup */
1359 if (error) g_error_free (error);
1365 control_stdin(GIOChannel *gio, GIOCondition condition) {
1367 gchar *ctl_line = NULL;
1370 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1371 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1374 parse_cmd_line(ctl_line);
1382 GIOChannel *chan = NULL;
1383 GError *error = NULL;
1385 chan = g_io_channel_unix_new(fileno(stdin));
1387 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1388 g_error ("Stdin: could not add watch\n");
1390 if (uzbl.state.verbose)
1391 printf ("Stdin: watch added successfully\n");
1394 g_error ("Stdin: Error while opening: %s\n", error->message);
1396 if (error) g_error_free (error);
1400 control_socket(GIOChannel *chan) {
1401 struct sockaddr_un remote;
1402 char buffer[512], *ctl_line;
1404 int sock, clientsock, n, done;
1407 sock = g_io_channel_unix_get_fd(chan);
1409 memset (buffer, 0, sizeof (buffer));
1411 t = sizeof (remote);
1412 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1416 memset (temp, 0, sizeof (temp));
1417 n = recv (clientsock, temp, 128, 0);
1419 buffer[strlen (buffer)] = '\0';
1423 strcat (buffer, temp);
1426 if (strcmp (buffer, "\n") < 0) {
1427 buffer[strlen (buffer) - 1] = '\0';
1429 buffer[strlen (buffer)] = '\0';
1432 ctl_line = g_strdup(buffer);
1433 parse_cmd_line (ctl_line);
1436 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1437 GError *error = NULL;
1440 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1441 if (ret == G_IO_STATUS_ERROR)
1442 g_error ("Error reading: %s\n", error->message);
1444 printf("Got line %s (%u bytes) \n",ctl_line, len);
1446 parse_line(ctl_line);
1454 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1455 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1456 if (unlink(uzbl.comm.socket_path) == -1)
1457 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1458 g_free(uzbl.comm.socket_path);
1459 uzbl.comm.socket_path = NULL;
1467 GIOChannel *chan = NULL;
1469 struct sockaddr_un local;
1470 gchar *path = build_stream_name(SOCKET, dir);
1472 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1474 local.sun_family = AF_UNIX;
1475 strcpy (local.sun_path, path);
1476 unlink (local.sun_path);
1478 len = strlen (local.sun_path) + sizeof (local.sun_family);
1479 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1480 if (uzbl.state.verbose)
1481 printf ("init_socket: opened in %s\n", path);
1484 if( (chan = g_io_channel_unix_new(sock)) ) {
1485 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1486 uzbl.comm.socket_path = path;
1489 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1491 /* if we got this far, there was an error; cleanup */
1498 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1499 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1501 // this function may be called very early when the templates are not set (yet), hence the checks
1503 update_title (void) {
1504 Behaviour *b = &uzbl.behave;
1507 if (b->show_status) {
1508 if (b->title_format_short) {
1509 parsed = expand_template(b->title_format_short);
1510 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1513 if (b->status_format) {
1514 parsed = expand_template(b->status_format);
1515 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1518 if (b->status_background) {
1520 gdk_color_parse (b->status_background, &color);
1521 //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)
1522 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1525 if (b->title_format_long) {
1526 parsed = expand_template(b->title_format_long);
1527 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1534 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1536 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1540 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1541 || 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)
1544 /* turn off insert mode (if always_insert_mode is not used) */
1545 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1546 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1551 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1554 if (event->keyval == GDK_Escape) {
1555 g_string_truncate(uzbl.state.keycmd, 0);
1560 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1561 if (event->keyval == GDK_Insert) {
1563 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1564 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1566 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1569 g_string_append (uzbl.state.keycmd, str);
1576 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1577 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1581 gboolean key_ret = FALSE;
1582 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1584 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1586 run_keycmd(key_ret);
1588 if (key_ret) return (!uzbl.behave.insert_mode);
1593 run_keycmd(const gboolean key_ret) {
1594 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1596 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1597 g_string_truncate(uzbl.state.keycmd, 0);
1598 parse_command(action->name, action->param);
1602 /* try if it's an incremental keycmd or one that takes args, and run it */
1603 GString* short_keys = g_string_new ("");
1604 GString* short_keys_inc = g_string_new ("");
1606 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1607 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1608 g_string_assign(short_keys_inc, short_keys->str);
1609 g_string_append_c(short_keys, '_');
1610 g_string_append_c(short_keys_inc, '*');
1612 gboolean exec_now = FALSE;
1613 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1614 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1615 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1616 if (key_ret) { /* just quit the incremental command on return */
1617 g_string_truncate(uzbl.state.keycmd, 0);
1619 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1623 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1624 GString* actionname = g_string_new ("");
1625 GString* actionparam = g_string_new ("");
1626 g_string_erase (parampart, 0, i+1);
1628 g_string_printf (actionname, action->name, parampart->str);
1630 g_string_printf (actionparam, action->param, parampart->str);
1631 parse_command(actionname->str, actionparam->str);
1632 g_string_free (actionname, TRUE);
1633 g_string_free (actionparam, TRUE);
1634 g_string_free (parampart, TRUE);
1636 g_string_truncate(uzbl.state.keycmd, 0);
1640 g_string_truncate(short_keys, short_keys->len - 1);
1642 g_string_free (short_keys, TRUE);
1643 g_string_free (short_keys_inc, TRUE);
1650 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1651 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
1653 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1654 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1656 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1657 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1658 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1659 g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1660 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1661 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1662 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1663 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1664 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1665 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1666 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1668 return scrolled_window;
1675 g->mainbar = gtk_hbox_new (FALSE, 0);
1677 g->mainbar_label = gtk_label_new ("");
1678 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1679 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1680 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1681 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1682 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1687 GtkWidget* create_window () {
1688 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1689 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1690 gtk_widget_set_name (window, "Uzbl browser");
1691 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1697 run_handler (const gchar *act, const gchar *args) {
1698 char **parts = g_strsplit(act, " ", 2);
1700 else if ((g_strcmp0(parts[0], "spawn") == 0)
1701 || (g_strcmp0(parts[0], "sh") == 0)) {
1703 GString *a = g_string_new ("");
1705 spawnparts = split_quoted(parts[1], FALSE);
1706 g_string_append_printf(a, "%s", spawnparts[0]);
1707 if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1708 for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1709 g_string_append_printf(a, " %s", spawnparts[i]);
1710 parse_command(parts[0], a->str);
1711 g_string_free (a, TRUE);
1712 g_strfreev (spawnparts);
1714 parse_command(parts[0], parts[1]);
1719 add_binding (const gchar *key, const gchar *act) {
1720 char **parts = g_strsplit(act, " ", 2);
1727 if (uzbl.state.verbose)
1728 printf ("Binding %-10s : %s\n", key, act);
1730 action = new_action(parts[0], parts[1]);
1731 g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
1737 get_xdg_var (XDG_Var xdg) {
1738 const gchar* actual_value = getenv (xdg.environmental);
1739 const gchar* home = getenv ("HOME");
1741 gchar* return_value = str_replace ("~", home, actual_value);
1743 if (! actual_value || strcmp (actual_value, "") == 0) {
1744 if (xdg.default_value) {
1745 return_value = str_replace ("~", home, xdg.default_value);
1747 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 = malloc (1024);
1760 gchar* temporary_string = NULL;
1764 buf = get_xdg_var (XDG[xdg_type]);
1765 strcpy (temporary_file, buf);
1766 strcat (temporary_file, filename);
1769 if (! file_exists (temporary_file) && xdg_type != 2) {
1770 buf = get_xdg_var (XDG[3 + xdg_type]);
1771 temporary_string = (char *) strtok_r (buf, ":", &saveptr);
1774 while (temporary_string && ! file_exists (temporary_file)) {
1775 strcpy (temporary_file, temporary_string);
1776 strcat (temporary_file, filename);
1777 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1781 if (file_exists (temporary_file)) {
1782 return temporary_file;
1790 State *s = &uzbl.state;
1791 Network *n = &uzbl.net;
1793 uzbl.behave.reset_command_mode = 1;
1795 if (!s->config_file) {
1796 s->config_file = find_xdg_file (0, "/uzbl/config");
1799 if (s->config_file) {
1800 GIOChannel *chan = NULL;
1801 gchar *readbuf = NULL;
1804 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1807 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1808 == G_IO_STATUS_NORMAL) {
1809 parse_cmd_line(readbuf);
1813 g_io_channel_unref (chan);
1814 if (uzbl.state.verbose)
1815 printf ("Config %s loaded\n", s->config_file);
1817 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1820 if (uzbl.state.verbose)
1821 printf ("No configuration file loaded.\n");
1823 if (!uzbl.behave.status_format)
1824 set_var_value("status_format", STATUS_DEFAULT);
1825 if (!uzbl.behave.title_format_long)
1826 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1827 if (!uzbl.behave.title_format_short)
1828 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1831 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1835 set_useragent(gchar *val) {
1840 gchar *ua = expand_template(val);
1842 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1846 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1849 if (!uzbl.behave.cookie_handler) return;
1851 gchar * stdout = NULL;
1852 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1853 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1854 gchar *action = g_strdup ("GET");
1855 SoupURI * soup_uri = soup_message_get_uri(msg);
1856 sharg_append(a, action);
1857 sharg_append(a, soup_uri->host);
1858 sharg_append(a, soup_uri->path);
1859 run_command(uzbl.behave.cookie_handler, 0, a->data, TRUE, &stdout); /* TODO: use handler */
1860 //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */
1862 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1865 g_array_free(a, TRUE);
1869 save_cookies (SoupMessage *msg, gpointer user_data){
1873 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1874 cookie = soup_cookie_to_set_cookie_header(ck->data);
1875 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1876 SoupURI * soup_uri = soup_message_get_uri(msg);
1877 gchar *action = strdup("PUT");
1878 sharg_append(a, action);
1879 sharg_append(a, soup_uri->host);
1880 sharg_append(a, soup_uri->path);
1881 sharg_append(a, cookie);
1882 run_command(uzbl.behave.cookie_handler, 0, a->data, FALSE, NULL);
1885 g_array_free(a, TRUE);
1891 main (int argc, char* argv[]) {
1892 gtk_init (&argc, &argv);
1893 if (!g_thread_supported ())
1894 g_thread_init (NULL);
1896 uzbl.state.executable_path = g_strdup(argv[0]);
1897 uzbl.state.selected_url = NULL;
1898 uzbl.state.searchtx = NULL;
1900 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1901 g_option_context_add_main_entries (context, entries, NULL);
1902 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1903 g_option_context_parse (context, &argc, &argv, NULL);
1904 g_option_context_free(context);
1905 /* initialize hash table */
1906 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1908 uzbl.net.soup_session = webkit_get_default_session();
1909 uzbl.state.keycmd = g_string_new("");
1911 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1912 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1913 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1914 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1916 if(uname(&uzbl.state.unameinfo) == -1)
1917 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1922 make_var_to_name_hash();
1924 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1926 uzbl.gui.scrolled_win = create_browser();
1929 /* initial packing */
1930 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1931 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1933 uzbl.gui.main_window = create_window ();
1934 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1937 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1938 gtk_widget_show_all (uzbl.gui.main_window);
1939 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1941 if (uzbl.state.verbose) {
1942 printf("Uzbl start location: %s\n", argv[0]);
1943 printf("window_id %i\n",(int) uzbl.xwin);
1944 printf("pid %i\n", getpid ());
1945 printf("name: %s\n", uzbl.state.instance_name);
1948 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1949 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1950 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1951 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1952 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1956 if (!uzbl.behave.show_status)
1957 gtk_widget_hide(uzbl.gui.mainbar);
1964 load_uri (uzbl.gui.web_view, uzbl.state.uri);
1970 return EXIT_SUCCESS;
1973 /* vi: set et ts=4: */