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 */
62 typedef const struct {
68 enum {TYPE_INT, TYPE_STRING};
73 } var_name_to_ptr[] = {
74 /* variable name pointer to variable in code string variable callback function */
75 /* ------------------------------------------------------------------------------------------------------------------- */
76 { "uri", {.ptr = (void *)&uzbl.state.uri, .type = TYPE_STRING, .func = cmd_load_uri}},
77 { "status_message", {.ptr = (void *)&uzbl.gui.sbar.msg, .type = TYPE_STRING, .func = update_title}},
78 { "show_status", {.ptr = (void *)&uzbl.behave.show_status, .type = TYPE_INT, .func = cmd_set_status}},
79 { "status_top", {.ptr = (void *)&uzbl.behave.status_top, .type = TYPE_INT, .func = move_statusbar}},
80 { "status_format", {.ptr = (void *)&uzbl.behave.status_format, .type = TYPE_STRING, .func = update_title}},
81 { "status_background", {.ptr = (void *)&uzbl.behave.status_background, .type = TYPE_STRING, .func = update_title}},
82 { "title_format_long", {.ptr = (void *)&uzbl.behave.title_format_long, .type = TYPE_STRING, .func = update_title}},
83 { "title_format_short", {.ptr = (void *)&uzbl.behave.title_format_short, .type = TYPE_STRING, .func = update_title}},
84 { "insert_mode", {.ptr = (void *)&uzbl.behave.insert_mode, .type = TYPE_INT, .func = NULL}},
85 { "always_insert_mode", {.ptr = (void *)&uzbl.behave.always_insert_mode, .type = TYPE_INT, .func = NULL}},
86 { "reset_command_mode", {.ptr = (void *)&uzbl.behave.reset_command_mode, .type = TYPE_INT, .func = NULL}},
87 { "modkey" , {.ptr = (void *)&uzbl.behave.modkey, .type = TYPE_STRING, .func = NULL}},
88 { "load_finish_handler",{.ptr = (void *)&uzbl.behave.load_finish_handler, .type = TYPE_STRING, .func = update_title}},
89 { "load_start_handler", {.ptr = (void *)&uzbl.behave.load_start_handler, .type = TYPE_STRING, .func = update_title}},
90 { "load_commit_handler",{.ptr = (void *)&uzbl.behave.load_commit_handler, .type = TYPE_STRING, .func = update_title}},
91 { "history_handler", {.ptr = (void *)&uzbl.behave.history_handler, .type = TYPE_STRING, .func = update_title}},
92 { "download_handler", {.ptr = (void *)&uzbl.behave.download_handler, .type = TYPE_STRING, .func = update_title}},
93 { "cookie_handler", {.ptr = (void *)&uzbl.behave.cookie_handler, .type = TYPE_STRING, .func = update_title}},
94 { "fifo_dir", {.ptr = (void *)&uzbl.behave.fifo_dir, .type = TYPE_STRING, .func = NULL}},
95 { "socket_dir", {.ptr = (void *)&uzbl.behave.socket_dir, .type = TYPE_STRING, .func = NULL}},
96 { "http_debug", {.ptr = (void *)&uzbl.behave.http_debug, .type = TYPE_INT, .func = NULL}},
97 { "default_font_size", {.ptr = (void *)&uzbl.behave.default_font_size, .type = TYPE_INT, .func = NULL}},
98 { "minimum_font_size", {.ptr = (void *)&uzbl.behave.minimum_font_size, .type = TYPE_INT, .func = NULL}},
99 { "shell_cmd", {.ptr = (void *)&uzbl.behave.shell_cmd, .type = TYPE_STRING, .func = NULL}},
100 { "proxy_url", {.ptr = (void *)&uzbl.net.proxy_url, .type = TYPE_STRING, .func = set_proxy_url}},
101 { "max_conns", {.ptr = (void *)&uzbl.net.max_conns, .type = TYPE_INT, .func = NULL}},
102 { "max_conns_host", {.ptr = (void *)&uzbl.net.max_conns_host, .type = TYPE_INT, .func = NULL}},
103 { "useragent", {.ptr = (void *)&uzbl.net.useragent, .type = TYPE_STRING, .func = NULL}},
104 { NULL, {.ptr = NULL, .type = TYPE_INT, .func = NULL}}
105 }, *n2v_p = var_name_to_ptr;
111 { "SHIFT", GDK_SHIFT_MASK }, // shift
112 { "LOCK", GDK_LOCK_MASK }, // capslock or shiftlock, depending on xserver's modmappings
113 { "CONTROL", GDK_CONTROL_MASK }, // control
114 { "MOD1", GDK_MOD1_MASK }, // 4th mod - normally alt but depends on modmappings
115 { "MOD2", GDK_MOD2_MASK }, // 5th mod
116 { "MOD3", GDK_MOD3_MASK }, // 6th mod
117 { "MOD4", GDK_MOD4_MASK }, // 7th mod
118 { "MOD5", GDK_MOD5_MASK }, // 8th mod
119 { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
120 { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
121 { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
122 { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
123 { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
124 { "SUPER", GDK_SUPER_MASK }, // super (since 2.10)
125 { "HYPER", GDK_HYPER_MASK }, // hyper (since 2.10)
126 { "META", GDK_META_MASK }, // meta (since 2.10)
131 /* construct a hash from the var_name_to_ptr array for quick access */
133 make_var_to_name_hash() {
134 uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
136 g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
141 /* commandline arguments (set initial values for the state variables) */
142 static GOptionEntry entries[] =
144 { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
145 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, "Whether to print all messages or just errors.", NULL },
146 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
147 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
148 { NULL, 0, 0, 0, NULL, NULL, NULL }
151 typedef void (*Command)(WebKitWebView*, const char *);
153 /* --- UTILITY FUNCTIONS --- */
159 snprintf(tmp, sizeof(tmp), "%i", val);
160 return g_strdup(tmp);
164 str_replace (const char* search, const char* replace, const char* string) {
168 buf = g_strsplit (string, search, -1);
169 ret = g_strjoinv (replace, buf);
176 setup_signal(int signr, sigfunc *shandler) {
177 struct sigaction nh, oh;
179 nh.sa_handler = shandler;
180 sigemptyset(&nh.sa_mask);
183 if(sigaction(signr, &nh, &oh) < 0)
191 if (uzbl.behave.fifo_dir)
192 unlink (uzbl.comm.fifo_path);
193 if (uzbl.behave.socket_dir)
194 unlink (uzbl.comm.socket_path);
196 g_free(uzbl.state.executable_path);
197 g_string_free(uzbl.state.keycmd, TRUE);
198 g_hash_table_destroy(uzbl.bindings);
199 g_hash_table_destroy(uzbl.behave.commands);
203 /* --- SIGNAL HANDLER --- */
206 catch_sigterm(int s) {
212 catch_sigint(int s) {
218 /* --- CALLBACKS --- */
221 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
224 (void) navigation_action;
225 (void) policy_decision;
227 const gchar* uri = webkit_network_request_get_uri (request);
228 if (uzbl.state.verbose)
229 printf("New window requested -> %s \n", uri);
230 new_window_load_uri(uri);
235 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
239 if (uzbl.state.selected_url != NULL) {
240 if (uzbl.state.verbose)
241 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
242 new_window_load_uri(uzbl.state.selected_url);
244 if (uzbl.state.verbose)
245 printf("New web view -> %s\n","Nothing to open, exiting");
251 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
254 if (uzbl.behave.download_handler) {
255 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
256 if (uzbl.state.verbose)
257 printf("Download -> %s\n",uri);
258 /* if urls not escaped, we may have to escape and quote uri before this call */
259 run_handler(uzbl.behave.download_handler, uri);
264 /* scroll a bar in a given direction */
266 scroll (GtkAdjustment* bar, const char *param) {
270 amount = g_ascii_strtod(param, &end);
271 if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
272 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
275 static void scroll_begin(WebKitWebView* page, const char *param) {
276 (void) page; (void) param;
277 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
280 static void scroll_end(WebKitWebView* page, const char *param) {
281 (void) page; (void) param;
282 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
283 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
286 static void scroll_vert(WebKitWebView* page, const char *param) {
288 scroll(uzbl.gui.bar_v, param);
291 static void scroll_horz(WebKitWebView* page, const char *param) {
293 scroll(uzbl.gui.bar_h, param);
298 if (!uzbl.behave.show_status) {
299 gtk_widget_hide(uzbl.gui.mainbar);
301 gtk_widget_show(uzbl.gui.mainbar);
307 toggle_status_cb (WebKitWebView* page, const char *param) {
311 if (uzbl.behave.show_status) {
312 gtk_widget_hide(uzbl.gui.mainbar);
314 gtk_widget_show(uzbl.gui.mainbar);
316 uzbl.behave.show_status = !uzbl.behave.show_status;
321 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
325 //Set selected_url state variable
326 g_free(uzbl.state.selected_url);
327 uzbl.state.selected_url = NULL;
329 uzbl.state.selected_url = g_strdup(link);
335 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
339 if (uzbl.gui.main_title)
340 g_free (uzbl.gui.main_title);
341 uzbl.gui.main_title = g_strdup (title);
346 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
349 uzbl.gui.sbar.load_progress = progress;
354 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
358 if (uzbl.behave.load_finish_handler)
359 run_handler(uzbl.behave.load_finish_handler, "");
363 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
367 if (uzbl.behave.load_start_handler)
368 run_handler(uzbl.behave.load_start_handler, "");
372 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
375 free (uzbl.state.uri);
376 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
377 uzbl.state.uri = g_string_free (newuri, FALSE);
378 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
379 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
382 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
383 if (uzbl.behave.load_commit_handler)
384 run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
388 destroy_cb (GtkWidget* widget, gpointer data) {
396 if (uzbl.behave.history_handler) {
398 struct tm * timeinfo;
401 timeinfo = localtime ( &rawtime );
402 strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
403 run_handler(uzbl.behave.history_handler, date);
408 /* VIEW funcs (little webkit wrappers) */
409 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, const char *param){(void)param; webkit_web_view_##name(page);}
411 VIEWFUNC(reload_bypass_cache)
412 VIEWFUNC(stop_loading)
419 /* -- command to callback/function map for things we cannot attach to any signals */
422 static struct {char *name; Command command;} cmdlist[] =
424 { "back", view_go_back },
425 { "forward", view_go_forward },
426 { "scroll_vert", scroll_vert },
427 { "scroll_horz", scroll_horz },
428 { "scroll_begin", scroll_begin },
429 { "scroll_end", scroll_end },
430 { "reload", view_reload, },
431 { "reload_ign_cache", view_reload_bypass_cache},
432 { "stop", view_stop_loading, },
433 { "zoom_in", view_zoom_in, }, //Can crash (when max zoom reached?).
434 { "zoom_out", view_zoom_out, },
436 { "script", run_js },
437 { "toggle_status", toggle_status_cb },
440 { "exit", close_uzbl },
441 { "search", search_forward_text },
442 { "search_reverse", search_reverse_text },
443 { "insert_mode", set_insert_mode },
451 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
453 for (i = 0; i < LENGTH(cmdlist); i++)
454 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
457 /* -- CORE FUNCTIONS -- */
460 free_action(gpointer act) {
461 Action *action = (Action*)act;
462 g_free(action->name);
464 g_free(action->param);
469 new_action(const gchar *name, const gchar *param) {
470 Action *action = g_new(Action, 1);
472 action->name = g_strdup(name);
474 action->param = g_strdup(param);
476 action->param = NULL;
482 file_exists (const char * filename) {
483 return (access(filename, F_OK) == 0);
487 set_insert_mode(WebKitWebView *page, const gchar *param) {
491 uzbl.behave.insert_mode = TRUE;
496 load_uri (WebKitWebView * web_view, const gchar *param) {
498 GString* newuri = g_string_new (param);
499 if (g_strrstr (param, "://") == NULL)
500 g_string_prepend (newuri, "http://");
501 /* if we do handle cookies, ask our handler for them */
502 webkit_web_view_load_uri (web_view, newuri->str);
503 g_string_free (newuri, TRUE);
508 run_js (WebKitWebView * web_view, const gchar *param) {
510 webkit_web_view_execute_script (web_view, param);
514 search_text (WebKitWebView *page, const char *param, const gboolean forward) {
515 if ((param) && (param[0] != '\0')) {
516 uzbl.state.searchtx = g_strdup(param);
518 if (uzbl.state.searchtx != NULL) {
519 if (uzbl.state.verbose)
520 printf ("Searching: %s\n", uzbl.state.searchtx);
521 webkit_web_view_unmark_text_matches (page);
522 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
523 webkit_web_view_set_highlight_text_matches (page, TRUE);
524 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
525 g_free(uzbl.state.searchtx);
526 uzbl.state.searchtx = NULL;
531 search_forward_text (WebKitWebView *page, const char *param) {
532 search_text(page, param, TRUE);
536 search_reverse_text (WebKitWebView *page, const char *param) {
537 search_text(page, param, FALSE);
541 new_window_load_uri (const gchar * uri) {
542 GString* to_execute = g_string_new ("");
543 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
545 for (i = 0; entries[i].long_name != NULL; i++) {
546 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
547 gchar** str = (gchar**)entries[i].arg_data;
549 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
553 if (uzbl.state.verbose)
554 printf("\n%s\n", to_execute->str);
555 g_spawn_command_line_async (to_execute->str, NULL);
556 g_string_free (to_execute, TRUE);
560 close_uzbl (WebKitWebView *page, const char *param) {
566 /* --Statusbar functions-- */
568 build_progressbar_ascii(int percent) {
572 GString *bar = g_string_new("");
574 l = (double)percent*((double)width/100.);
575 l = (int)(l+.5)>=(int)l ? l+.5 : l;
577 for(i=0; i<(int)l; i++)
578 g_string_append(bar, "=");
581 g_string_append(bar, "·");
583 return g_string_free(bar, FALSE);
588 const GScannerConfig scan_config = {
591 ) /* cset_skip_characters */,
596 ) /* cset_identifier_first */,
603 ) /* cset_identifier_nth */,
604 ( "" ) /* cpair_comment_single */,
606 TRUE /* case_sensitive */,
608 FALSE /* skip_comment_multi */,
609 FALSE /* skip_comment_single */,
610 FALSE /* scan_comment_multi */,
611 TRUE /* scan_identifier */,
612 TRUE /* scan_identifier_1char */,
613 FALSE /* scan_identifier_NULL */,
614 TRUE /* scan_symbols */,
615 FALSE /* scan_binary */,
616 FALSE /* scan_octal */,
617 FALSE /* scan_float */,
618 FALSE /* scan_hex */,
619 FALSE /* scan_hex_dollar */,
620 FALSE /* scan_string_sq */,
621 FALSE /* scan_string_dq */,
622 TRUE /* numbers_2_int */,
623 FALSE /* int_2_float */,
624 FALSE /* identifier_2_string */,
625 FALSE /* char_2_token */,
626 FALSE /* symbol_2_token */,
627 TRUE /* scope_0_fallback */,
632 uzbl.scan = g_scanner_new(&scan_config);
633 while(symp->symbol_name) {
634 g_scanner_scope_add_symbol(uzbl.scan, 0,
636 GINT_TO_POINTER(symp->symbol_token));
642 expand_template(const char *template) {
643 if(!template) return NULL;
645 GTokenType token = G_TOKEN_NONE;
646 GString *ret = g_string_new("");
650 g_scanner_input_text(uzbl.scan, template, strlen(template));
651 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
652 token = g_scanner_get_next_token(uzbl.scan);
654 if(token == G_TOKEN_SYMBOL) {
655 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
658 buf = uzbl.state.uri?
659 g_markup_printf_escaped("%s", uzbl.state.uri) :
661 g_string_append(ret, buf);
665 buf = itos(uzbl.gui.sbar.load_progress);
666 g_string_append(ret, buf);
669 case SYM_LOADPRGSBAR:
670 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
671 g_string_append(ret, buf);
675 buf = uzbl.gui.main_title?
676 g_markup_printf_escaped("%s", uzbl.gui.main_title) :
678 g_string_append(ret, buf);
681 case SYM_SELECTED_URI:
682 buf = uzbl.state.selected_url?
683 g_markup_printf_escaped("%s", uzbl.state.selected_url) :
685 g_string_append(ret, buf);
689 buf = itos(uzbl.xwin);
691 uzbl.state.instance_name?uzbl.state.instance_name:buf);
695 buf = uzbl.state.keycmd->str?
696 g_markup_printf_escaped("%s", uzbl.state.keycmd->str) :
698 g_string_append(ret, buf);
703 uzbl.behave.insert_mode?"[I]":"[C]");
707 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
711 buf = itos(WEBKIT_MAJOR_VERSION);
712 g_string_append(ret, buf);
716 buf = itos(WEBKIT_MINOR_VERSION);
717 g_string_append(ret, buf);
721 buf = itos(WEBKIT_MICRO_VERSION);
722 g_string_append(ret, buf);
726 g_string_append(ret, uzbl.state.unameinfo.sysname);
729 g_string_append(ret, uzbl.state.unameinfo.nodename);
732 g_string_append(ret, uzbl.state.unameinfo.release);
735 g_string_append(ret, uzbl.state.unameinfo.version);
738 g_string_append(ret, uzbl.state.unameinfo.machine);
741 g_string_append(ret, ARCH);
745 g_string_append(ret, uzbl.state.unameinfo.domainname);
749 g_string_append(ret, COMMIT);
755 else if(token == G_TOKEN_INT) {
756 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
757 g_string_append(ret, buf);
760 else if(token == G_TOKEN_IDENTIFIER) {
761 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
763 else if(token == G_TOKEN_CHAR) {
764 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
768 return g_string_free(ret, FALSE);
770 /* --End Statusbar functions-- */
773 sharg_append(GArray *a, const gchar *str) {
774 const gchar *s = (str ? str : "");
775 g_array_append_val(a, s);
778 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
780 run_command (const gchar *command, const guint npre, const gchar **args,
781 const gboolean sync, char **stdout) {
782 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
785 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
786 gchar *pid = itos(getpid());
787 gchar *xwin = itos(uzbl.xwin);
789 sharg_append(a, command);
790 for (i = 0; i < npre; i++) /* add n args before the default vars */
791 sharg_append(a, args[i]);
792 sharg_append(a, uzbl.state.config_file);
793 sharg_append(a, pid);
794 sharg_append(a, xwin);
795 sharg_append(a, uzbl.comm.fifo_path);
796 sharg_append(a, uzbl.comm.socket_path);
797 sharg_append(a, uzbl.state.uri);
798 sharg_append(a, uzbl.gui.main_title);
800 for (i = npre; i < g_strv_length((gchar**)args); i++)
801 sharg_append(a, args[i]);
803 if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
804 NULL, NULL, stdout, NULL, NULL, &err);
805 else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
806 NULL, NULL, NULL, &err);
808 if (uzbl.state.verbose) {
809 GString *s = g_string_new("spawned:");
810 for (i = 0; i < (a->len); i++) {
811 gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
812 g_string_append_printf(s, " %s", qarg);
815 g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
816 printf("%s\n", s->str);
817 g_string_free(s, TRUE);
820 g_printerr("error on run_command: %s\n", err->message);
825 g_array_free (a, TRUE);
830 split_quoted(const gchar* src, const gboolean unquote) {
831 /* split on unquoted space, return array of strings;
832 remove a layer of quotes and backslashes if unquote */
835 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
836 GString *s = g_string_new ("");
840 for (p = src; *p != '\0'; p++) {
841 if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
842 else if (*p == '\\') { g_string_append_c(s, *p++);
843 g_string_append_c(s, *p); }
844 else if ((*p == '"') && unquote && !sq) dq = !dq;
845 else if (*p == '"' && !sq) { g_string_append_c(s, *p);
847 else if ((*p == '\'') && unquote && !dq) sq = !sq;
848 else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
850 else if ((*p == ' ') && !dq && !sq) {
851 dup = g_strdup(s->str);
852 g_array_append_val(a, dup);
853 g_string_truncate(s, 0);
854 } else g_string_append_c(s, *p);
856 dup = g_strdup(s->str);
857 g_array_append_val(a, dup);
858 ret = (gchar**)a->data;
859 g_array_free (a, FALSE);
860 g_string_free (s, FALSE);
865 spawn(WebKitWebView *web_view, const char *param) {
867 //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
868 gchar **cmd = split_quoted(param, TRUE);
869 if (cmd) run_command(cmd[0], 0, &cmd[1], FALSE, NULL);
870 g_strfreev ((gchar**)cmd);
874 spawn_sh(WebKitWebView *web_view, const char *param) {
876 if (!uzbl.behave.shell_cmd) {
877 g_printerr ("spawn_sh: shell_cmd is not set!\n");
882 gchar *spacer = g_strdup("");
883 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
884 gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
885 gchar **p = split_quoted(param, TRUE);
886 for (i = 1; i < g_strv_length(cmd); i++)
887 sharg_append(a, cmd[i]);
888 sharg_append(a, p[0]); /* the first param comes right after shell_cmd;
889 the rest come after default args */
890 sharg_append(a, spacer);
891 for (i = 1; i < g_strv_length(p); i++)
892 sharg_append(a, p[i]);
893 if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, a->data, FALSE, NULL);
897 g_array_free (a, FALSE);
901 parse_command(const char *cmd, const char *param) {
904 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd)))
905 c(uzbl.gui.web_view, param);
907 fprintf (stderr, "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( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
930 if(c->type == TYPE_STRING)
931 printf("VAR: %s VALUE: %s\n", name, (char *)*c->ptr);
932 else if(c->type == TYPE_INT)
933 printf("VAR: %s VALUE: %d\n", name, (int)*c->ptr);
942 if(*uzbl.net.proxy_url == ' '
943 || uzbl.net.proxy_url == NULL) {
944 soup_session_remove_feature_by_type(uzbl.net.soup_session,
945 (GType) SOUP_SESSION_PROXY_URI);
948 suri = soup_uri_new(uzbl.net.proxy_url);
949 g_object_set(G_OBJECT(uzbl.net.soup_session),
950 SOUP_SESSION_PROXY_URI,
959 load_uri(uzbl.gui.web_view, uzbl.state.uri);
964 gtk_widget_ref(uzbl.gui.scrolled_win);
965 gtk_widget_ref(uzbl.gui.mainbar);
966 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
967 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
969 if(uzbl.behave.status_top) {
970 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
971 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
974 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
975 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
977 gtk_widget_unref(uzbl.gui.scrolled_win);
978 gtk_widget_unref(uzbl.gui.mainbar);
979 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
984 var_is(const char *x, const char *y) {
985 return (strcmp(x, y) == 0 ? TRUE : FALSE );
989 set_var_value(gchar *name, gchar *val) {
991 uzbl_cmdprop *c = NULL;
995 if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
996 /* check for the variable type */
997 if (c->type == TYPE_STRING) {
999 *c->ptr = g_strdup(val);
1000 } else if(c->type == TYPE_INT) {
1001 *c->ptr = (int)strtoul(val, &endp, 10);
1004 /* invoke a command specific function */
1008 /* this will be removed as soon as we have converted to
1009 * the callback interface
1013 if(var_is("fifo_dir", name)) {
1015 buf = init_fifo(val);
1016 p = buf?buf:g_strdup("");
1018 else if(var_is("socket_dir", name)) {
1020 buf = init_socket(val);
1021 p = buf?buf:g_strdup("");
1023 else if(var_is("modkey", name)) {
1026 p = g_utf8_strup(val, -1);
1027 uzbl.behave.modmask = 0;
1028 for (i = 0; modkeys[i].key != NULL; i++) {
1029 if (g_strrstr(p, modkeys[i].key))
1030 uzbl.behave.modmask |= modkeys[i].mask;
1033 else if(var_is("useragent", name)) {
1035 buf = set_useragent(val);
1036 p = buf?buf:g_strdup("");
1038 else if(var_is("always_insert_mode", name)) {
1039 uzbl.behave.insert_mode =
1040 uzbl.behave.always_insert_mode ? TRUE : FALSE;
1043 else if (var_is("max_conns", name)) {
1044 g_object_set(G_OBJECT(uzbl.net.soup_session),
1045 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
1047 else if (var_is("max_conns_host", name)) {
1048 g_object_set(G_OBJECT(uzbl.net.soup_session),
1049 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
1051 else if (var_is("http_debug", name)) {
1052 soup_session_remove_feature
1053 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1054 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1055 /*g_free(uzbl.net.soup_logger);*/
1057 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1058 soup_session_add_feature(uzbl.net.soup_session,
1059 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1061 else if (var_is("default_font_size", name)) {
1062 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1063 g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.default_font_size, NULL);
1065 else if (var_is("minimum_font_size", name)) {
1066 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1067 g_object_set (G_OBJECT(ws), "minimum-font-size", uzbl.behave.minimum_font_size, NULL);
1074 runcmd(WebKitWebView* page, const char *param) {
1076 parse_cmd_line(param);
1080 parse_cmd_line(const char *ctl_line) {
1084 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1085 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1086 if(tokens[0][0] == 0) {
1087 set_var_value(tokens[1], tokens[2]);
1091 printf("Error in command: %s\n", tokens[0]);
1094 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1095 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1096 if(tokens[0][0] == 0) {
1097 get_var_value(tokens[1]);
1101 printf("Error in command: %s\n", tokens[0]);
1104 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1105 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1106 if(tokens[0][0] == 0) {
1107 add_binding(tokens[1], tokens[2]);
1111 printf("Error in command: %s\n", tokens[0]);
1114 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1115 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1116 if(tokens[0][0] == 0) {
1117 parse_command(tokens[1], tokens[2]);
1121 printf("Error in command: %s\n", tokens[0]);
1123 /* KEYCMD command */
1124 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1125 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1126 if(tokens[0][0] == 0) {
1127 /* should incremental commands want each individual "keystroke"
1128 sent in a loop or the whole string in one go like now? */
1129 g_string_assign(uzbl.state.keycmd, tokens[1]);
1131 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1138 else if( (ctl_line[0] == '#')
1139 || (ctl_line[0] == ' ')
1140 || (ctl_line[0] == '\n'))
1141 ; /* ignore these lines */
1143 printf("Command not understood (%s)\n", ctl_line);
1149 build_stream_name(int type, const gchar* dir) {
1151 State *s = &uzbl.state;
1154 xwin_str = itos((int)uzbl.xwin);
1156 str = g_strdup_printf
1157 ("%s/uzbl_fifo_%s", dir,
1158 s->instance_name ? s->instance_name : xwin_str);
1159 } else if (type == SOCKET) {
1160 str = g_strdup_printf
1161 ("%s/uzbl_socket_%s", dir,
1162 s->instance_name ? s->instance_name : xwin_str );
1169 control_fifo(GIOChannel *gio, GIOCondition condition) {
1170 if (uzbl.state.verbose)
1171 printf("triggered\n");
1176 if (condition & G_IO_HUP)
1177 g_error ("Fifo: Read end of pipe died!\n");
1180 g_error ("Fifo: GIOChannel broke\n");
1182 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1183 if (ret == G_IO_STATUS_ERROR) {
1184 g_error ("Fifo: Error reading: %s\n", err->message);
1188 parse_cmd_line(ctl_line);
1195 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1196 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1197 if (unlink(uzbl.comm.fifo_path) == -1)
1198 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1199 g_free(uzbl.comm.fifo_path);
1200 uzbl.comm.fifo_path = NULL;
1203 if (*dir == ' ') { /* space unsets the variable */
1207 GIOChannel *chan = NULL;
1208 GError *error = NULL;
1209 gchar *path = build_stream_name(FIFO, dir);
1211 if (!file_exists(path)) {
1212 if (mkfifo (path, 0666) == 0) {
1213 // 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.
1214 chan = g_io_channel_new_file(path, "r+", &error);
1216 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1217 if (uzbl.state.verbose)
1218 printf ("init_fifo: created successfully as %s\n", path);
1219 uzbl.comm.fifo_path = path;
1221 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1222 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1223 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1224 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1226 /* if we got this far, there was an error; cleanup */
1227 if (error) g_error_free (error);
1233 control_stdin(GIOChannel *gio, GIOCondition condition) {
1235 gchar *ctl_line = NULL;
1238 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1239 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1242 parse_cmd_line(ctl_line);
1250 GIOChannel *chan = NULL;
1251 GError *error = NULL;
1253 chan = g_io_channel_unix_new(fileno(stdin));
1255 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1256 g_error ("Stdin: could not add watch\n");
1258 if (uzbl.state.verbose)
1259 printf ("Stdin: watch added successfully\n");
1262 g_error ("Stdin: Error while opening: %s\n", error->message);
1264 if (error) g_error_free (error);
1268 control_socket(GIOChannel *chan) {
1269 struct sockaddr_un remote;
1270 char buffer[512], *ctl_line;
1272 int sock, clientsock, n, done;
1275 sock = g_io_channel_unix_get_fd(chan);
1277 memset (buffer, 0, sizeof (buffer));
1279 t = sizeof (remote);
1280 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1284 memset (temp, 0, sizeof (temp));
1285 n = recv (clientsock, temp, 128, 0);
1287 buffer[strlen (buffer)] = '\0';
1291 strcat (buffer, temp);
1294 if (strcmp (buffer, "\n") < 0) {
1295 buffer[strlen (buffer) - 1] = '\0';
1297 buffer[strlen (buffer)] = '\0';
1300 ctl_line = g_strdup(buffer);
1301 parse_cmd_line (ctl_line);
1304 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1305 GError *error = NULL;
1308 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1309 if (ret == G_IO_STATUS_ERROR)
1310 g_error ("Error reading: %s\n", error->message);
1312 printf("Got line %s (%u bytes) \n",ctl_line, len);
1314 parse_line(ctl_line);
1322 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1323 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1324 if (unlink(uzbl.comm.socket_path) == -1)
1325 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1326 g_free(uzbl.comm.socket_path);
1327 uzbl.comm.socket_path = NULL;
1335 GIOChannel *chan = NULL;
1337 struct sockaddr_un local;
1338 gchar *path = build_stream_name(SOCKET, dir);
1340 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1342 local.sun_family = AF_UNIX;
1343 strcpy (local.sun_path, path);
1344 unlink (local.sun_path);
1346 len = strlen (local.sun_path) + sizeof (local.sun_family);
1347 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1348 if (uzbl.state.verbose)
1349 printf ("init_socket: opened in %s\n", path);
1352 if( (chan = g_io_channel_unix_new(sock)) ) {
1353 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1354 uzbl.comm.socket_path = path;
1357 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1359 /* if we got this far, there was an error; cleanup */
1366 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1367 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1369 // this function may be called very early when the templates are not set (yet), hence the checks
1371 update_title (void) {
1372 Behaviour *b = &uzbl.behave;
1375 if (b->show_status) {
1376 if (b->title_format_short) {
1377 parsed = expand_template(b->title_format_short);
1378 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1381 if (b->status_format) {
1382 parsed = expand_template(b->status_format);
1383 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1386 if (b->status_background) {
1388 gdk_color_parse (b->status_background, &color);
1389 //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)
1390 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1393 if (b->title_format_long) {
1394 parsed = expand_template(b->title_format_long);
1395 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1402 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1404 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1408 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1409 || 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)
1412 /* turn off insert mode (if always_insert_mode is not used) */
1413 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1414 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1419 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1422 if (event->keyval == GDK_Escape) {
1423 g_string_truncate(uzbl.state.keycmd, 0);
1428 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1429 if (event->keyval == GDK_Insert) {
1431 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1432 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1434 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1437 g_string_append (uzbl.state.keycmd, str);
1444 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1445 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1449 gboolean key_ret = FALSE;
1450 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1452 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1454 run_keycmd(key_ret);
1456 if (key_ret) return (!uzbl.behave.insert_mode);
1461 run_keycmd(const gboolean key_ret) {
1462 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1464 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1465 g_string_truncate(uzbl.state.keycmd, 0);
1466 parse_command(action->name, action->param);
1470 /* try if it's an incremental keycmd or one that takes args, and run it */
1471 GString* short_keys = g_string_new ("");
1472 GString* short_keys_inc = g_string_new ("");
1474 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1475 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1476 g_string_assign(short_keys_inc, short_keys->str);
1477 g_string_append_c(short_keys, '_');
1478 g_string_append_c(short_keys_inc, '*');
1480 gboolean exec_now = FALSE;
1481 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1482 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1483 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1484 if (key_ret) { /* just quit the incremental command on return */
1485 g_string_truncate(uzbl.state.keycmd, 0);
1487 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1491 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1492 GString* actionname = g_string_new ("");
1493 GString* actionparam = g_string_new ("");
1494 g_string_erase (parampart, 0, i+1);
1496 g_string_printf (actionname, action->name, parampart->str);
1498 g_string_printf (actionparam, action->param, parampart->str);
1499 parse_command(actionname->str, actionparam->str);
1500 g_string_free (actionname, TRUE);
1501 g_string_free (actionparam, TRUE);
1502 g_string_free (parampart, TRUE);
1504 g_string_truncate(uzbl.state.keycmd, 0);
1508 g_string_truncate(short_keys, short_keys->len - 1);
1510 g_string_free (short_keys, TRUE);
1511 g_string_free (short_keys_inc, TRUE);
1518 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1519 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
1521 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1522 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1524 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1525 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1526 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1527 g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1528 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1529 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1530 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1531 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1532 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1533 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1534 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1536 return scrolled_window;
1543 g->mainbar = gtk_hbox_new (FALSE, 0);
1545 g->mainbar_label = gtk_label_new ("");
1546 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1547 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1548 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1549 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1550 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1555 GtkWidget* create_window () {
1556 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1557 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1558 gtk_widget_set_name (window, "Uzbl browser");
1559 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1565 run_handler (const gchar *act, const gchar *args) {
1566 char **parts = g_strsplit(act, " ", 2);
1568 else if ((g_strcmp0(parts[0], "spawn") == 0)
1569 || (g_strcmp0(parts[0], "sh") == 0)) {
1571 GString *a = g_string_new ("");
1573 spawnparts = split_quoted(parts[1], FALSE);
1574 g_string_append_printf(a, "%s", spawnparts[0]);
1575 if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1576 for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1577 g_string_append_printf(a, " %s", spawnparts[i]);
1578 parse_command(parts[0], a->str);
1579 g_string_free (a, TRUE);
1580 g_strfreev (spawnparts);
1582 parse_command(parts[0], parts[1]);
1587 add_binding (const gchar *key, const gchar *act) {
1588 char **parts = g_strsplit(act, " ", 2);
1595 if (uzbl.state.verbose)
1596 printf ("Binding %-10s : %s\n", key, act);
1598 action = new_action(parts[0], parts[1]);
1599 g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
1605 get_xdg_var (XDG_Var xdg) {
1606 const gchar* actual_value = getenv (xdg.environmental);
1607 const gchar* home = getenv ("HOME");
1609 gchar* return_value = str_replace ("~", home, actual_value);
1611 if (! actual_value || strcmp (actual_value, "") == 0) {
1612 if (xdg.default_value) {
1613 return_value = str_replace ("~", home, xdg.default_value);
1615 return_value = NULL;
1618 return return_value;
1622 find_xdg_file (int xdg_type, char* filename) {
1623 /* xdg_type = 0 => config
1624 xdg_type = 1 => data
1625 xdg_type = 2 => cache*/
1627 gchar* temporary_file = malloc (1024);
1628 gchar* temporary_string = NULL;
1632 buf = get_xdg_var (XDG[xdg_type]);
1633 strcpy (temporary_file, buf);
1634 strcat (temporary_file, filename);
1637 if (! file_exists (temporary_file) && xdg_type != 2) {
1638 buf = get_xdg_var (XDG[3 + xdg_type]);
1639 temporary_string = (char *) strtok_r (buf, ":", &saveptr);
1642 while (temporary_string && ! file_exists (temporary_file)) {
1643 strcpy (temporary_file, temporary_string);
1644 strcat (temporary_file, filename);
1645 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1649 if (file_exists (temporary_file)) {
1650 return temporary_file;
1658 State *s = &uzbl.state;
1659 Network *n = &uzbl.net;
1661 uzbl.behave.reset_command_mode = 1;
1663 if (!s->config_file) {
1664 s->config_file = find_xdg_file (0, "/uzbl/config");
1667 if (s->config_file) {
1668 GIOChannel *chan = NULL;
1669 gchar *readbuf = NULL;
1672 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1675 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1676 == G_IO_STATUS_NORMAL) {
1677 parse_cmd_line(readbuf);
1681 g_io_channel_unref (chan);
1682 if (uzbl.state.verbose)
1683 printf ("Config %s loaded\n", s->config_file);
1685 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1688 if (uzbl.state.verbose)
1689 printf ("No configuration file loaded.\n");
1691 if (!uzbl.behave.status_format)
1692 set_var_value("status_format", STATUS_DEFAULT);
1693 if (!uzbl.behave.title_format_long)
1694 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1695 if (!uzbl.behave.title_format_short)
1696 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1699 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1703 set_useragent(gchar *val) {
1708 gchar *ua = expand_template(val);
1710 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1714 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1717 if (!uzbl.behave.cookie_handler) return;
1719 gchar * stdout = NULL;
1720 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1721 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1722 gchar *action = g_strdup ("GET");
1723 SoupURI * soup_uri = soup_message_get_uri(msg);
1724 sharg_append(a, action);
1725 sharg_append(a, soup_uri->host);
1726 sharg_append(a, soup_uri->path);
1727 run_command(uzbl.behave.cookie_handler, 0, a->data, TRUE, &stdout); /* TODO: use handler */
1728 //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */
1730 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1733 g_array_free(a, TRUE);
1737 save_cookies (SoupMessage *msg, gpointer user_data){
1741 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1742 cookie = soup_cookie_to_set_cookie_header(ck->data);
1743 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1744 SoupURI * soup_uri = soup_message_get_uri(msg);
1745 gchar *action = strdup("PUT");
1746 sharg_append(a, action);
1747 sharg_append(a, soup_uri->host);
1748 sharg_append(a, soup_uri->path);
1749 sharg_append(a, cookie);
1750 run_command(uzbl.behave.cookie_handler, 0, a->data, FALSE, NULL);
1753 g_array_free(a, TRUE);
1760 main (int argc, char* argv[]) {
1761 gtk_init (&argc, &argv);
1762 if (!g_thread_supported ())
1763 g_thread_init (NULL);
1765 uzbl.state.executable_path = g_strdup(argv[0]);
1766 uzbl.state.selected_url = NULL;
1767 uzbl.state.searchtx = NULL;
1769 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1770 g_option_context_add_main_entries (context, entries, NULL);
1771 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1772 g_option_context_parse (context, &argc, &argv, NULL);
1773 g_option_context_free(context);
1774 /* initialize hash table */
1775 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1777 uzbl.net.soup_session = webkit_get_default_session();
1778 uzbl.state.keycmd = g_string_new("");
1780 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1781 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1782 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1783 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1785 if(uname(&uzbl.state.unameinfo) == -1)
1786 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1791 make_var_to_name_hash();
1794 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1796 uzbl.gui.scrolled_win = create_browser();
1799 /* initial packing */
1800 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1801 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1803 uzbl.gui.main_window = create_window ();
1804 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1807 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1808 gtk_widget_show_all (uzbl.gui.main_window);
1809 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1811 if (uzbl.state.verbose) {
1812 printf("Uzbl start location: %s\n", argv[0]);
1813 printf("window_id %i\n",(int) uzbl.xwin);
1814 printf("pid %i\n", getpid ());
1815 printf("name: %s\n", uzbl.state.instance_name);
1818 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1819 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1820 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1821 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1822 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1826 if (!uzbl.behave.show_status)
1827 gtk_widget_hide(uzbl.gui.mainbar);
1834 load_uri (uzbl.gui.web_view, uzbl.state.uri);
1839 return EXIT_SUCCESS;
1842 /* vi: set et ts=4: */