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>
60 typedef void (*Command)(WebKitWebView*, GArray *argv);
62 /* commandline arguments (set initial values for the state variables) */
63 static GOptionEntry entries[] =
65 { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
66 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, "Whether to print all messages or just errors.", NULL },
67 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
68 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
69 { NULL, 0, 0, 0, NULL, NULL, NULL }
73 /* associate command names to their properties */
74 typedef const struct {
80 enum {TYPE_INT, TYPE_STRING};
85 } var_name_to_ptr[] = {
86 /* variable name pointer to variable in code variable type callback function */
87 /* ------------------------------------------------------------------------------------------------------------------- */
88 { "uri", {.ptr = (void *)&uzbl.state.uri, .type = TYPE_STRING, .func = cmd_load_uri}},
89 { "status_message", {.ptr = (void *)&uzbl.gui.sbar.msg, .type = TYPE_STRING, .func = update_title}},
90 { "show_status", {.ptr = (void *)&uzbl.behave.show_status, .type = TYPE_INT, .func = cmd_set_status}},
91 { "status_top", {.ptr = (void *)&uzbl.behave.status_top, .type = TYPE_INT, .func = move_statusbar}},
92 { "status_format", {.ptr = (void *)&uzbl.behave.status_format, .type = TYPE_STRING, .func = update_title}},
93 { "status_background", {.ptr = (void *)&uzbl.behave.status_background, .type = TYPE_STRING, .func = update_title}},
94 { "title_format_long", {.ptr = (void *)&uzbl.behave.title_format_long, .type = TYPE_STRING, .func = update_title}},
95 { "title_format_short", {.ptr = (void *)&uzbl.behave.title_format_short, .type = TYPE_STRING, .func = update_title}},
96 { "insert_mode", {.ptr = (void *)&uzbl.behave.insert_mode, .type = TYPE_INT, .func = NULL}},
97 { "always_insert_mode", {.ptr = (void *)&uzbl.behave.always_insert_mode, .type = TYPE_INT, .func = cmd_always_insert_mode}},
98 { "reset_command_mode", {.ptr = (void *)&uzbl.behave.reset_command_mode, .type = TYPE_INT, .func = NULL}},
99 { "modkey", {.ptr = (void *)&uzbl.behave.modkey, .type = TYPE_STRING, .func = cmd_modkey}},
100 { "load_finish_handler",{.ptr = (void *)&uzbl.behave.load_finish_handler, .type = TYPE_STRING, .func = NULL}},
101 { "load_start_handler", {.ptr = (void *)&uzbl.behave.load_start_handler, .type = TYPE_STRING, .func = NULL}},
102 { "load_commit_handler",{.ptr = (void *)&uzbl.behave.load_commit_handler, .type = TYPE_STRING, .func = NULL}},
103 { "history_handler", {.ptr = (void *)&uzbl.behave.history_handler, .type = TYPE_STRING, .func = NULL}},
104 { "download_handler", {.ptr = (void *)&uzbl.behave.download_handler, .type = TYPE_STRING, .func = NULL}},
105 { "cookie_handler", {.ptr = (void *)&uzbl.behave.cookie_handler, .type = TYPE_STRING, .func = NULL}},
106 { "fifo_dir", {.ptr = (void *)&uzbl.behave.fifo_dir, .type = TYPE_STRING, .func = cmd_fifo_dir}},
107 { "socket_dir", {.ptr = (void *)&uzbl.behave.socket_dir, .type = TYPE_STRING, .func = cmd_socket_dir}},
108 { "http_debug", {.ptr = (void *)&uzbl.behave.http_debug, .type = TYPE_INT, .func = cmd_http_debug}},
109 { "default_font_size", {.ptr = (void *)&uzbl.behave.default_font_size, .type = TYPE_INT, .func = cmd_default_font_size}},
110 { "minimum_font_size", {.ptr = (void *)&uzbl.behave.minimum_font_size, .type = TYPE_INT, .func = cmd_minimum_font_size}},
111 { "shell_cmd", {.ptr = (void *)&uzbl.behave.shell_cmd, .type = TYPE_STRING, .func = NULL}},
112 { "proxy_url", {.ptr = (void *)&uzbl.net.proxy_url, .type = TYPE_STRING, .func = set_proxy_url}},
113 { "max_conns", {.ptr = (void *)&uzbl.net.max_conns, .type = TYPE_INT, .func = cmd_max_conns}},
114 { "max_conns_host", {.ptr = (void *)&uzbl.net.max_conns_host, .type = TYPE_INT, .func = cmd_max_conns_host}},
115 { "useragent", {.ptr = (void *)&uzbl.net.useragent, .type = TYPE_STRING, .func = cmd_useragent}},
116 { NULL, {.ptr = NULL, .type = TYPE_INT, .func = NULL}}
117 }, *n2v_p = var_name_to_ptr;
123 { "SHIFT", GDK_SHIFT_MASK }, // shift
124 { "LOCK", GDK_LOCK_MASK }, // capslock or shiftlock, depending on xserver's modmappings
125 { "CONTROL", GDK_CONTROL_MASK }, // control
126 { "MOD1", GDK_MOD1_MASK }, // 4th mod - normally alt but depends on modmappings
127 { "MOD2", GDK_MOD2_MASK }, // 5th mod
128 { "MOD3", GDK_MOD3_MASK }, // 6th mod
129 { "MOD4", GDK_MOD4_MASK }, // 7th mod
130 { "MOD5", GDK_MOD5_MASK }, // 8th mod
131 { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
132 { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
133 { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
134 { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
135 { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
136 { "SUPER", GDK_SUPER_MASK }, // super (since 2.10)
137 { "HYPER", GDK_HYPER_MASK }, // hyper (since 2.10)
138 { "META", GDK_META_MASK }, // meta (since 2.10)
143 /* construct a hash from the var_name_to_ptr array for quick access */
145 make_var_to_name_hash() {
146 uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
148 g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, (gpointer) &n2v_p->cp);
154 /* --- UTILITY FUNCTIONS --- */
160 snprintf(tmp, sizeof(tmp), "%i", val);
161 return g_strdup(tmp);
165 argv_idx(const GArray *a, const guint idx) { return g_array_index(a, gchar*, idx); }
168 str_replace (const char* search, const char* replace, const char* string) {
172 buf = g_strsplit (string, search, -1);
173 ret = g_strjoinv (replace, buf);
180 setup_signal(int signr, sigfunc *shandler) {
181 struct sigaction nh, oh;
183 nh.sa_handler = shandler;
184 sigemptyset(&nh.sa_mask);
187 if(sigaction(signr, &nh, &oh) < 0)
195 if (uzbl.behave.fifo_dir)
196 unlink (uzbl.comm.fifo_path);
197 if (uzbl.behave.socket_dir)
198 unlink (uzbl.comm.socket_path);
200 g_free(uzbl.state.executable_path);
201 g_string_free(uzbl.state.keycmd, TRUE);
202 g_hash_table_destroy(uzbl.bindings);
203 g_hash_table_destroy(uzbl.behave.commands);
207 /* --- SIGNAL HANDLER --- */
210 catch_sigterm(int s) {
216 catch_sigint(int s) {
222 /* --- CALLBACKS --- */
225 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
228 (void) navigation_action;
229 (void) policy_decision;
231 const gchar* uri = webkit_network_request_get_uri (request);
232 if (uzbl.state.verbose)
233 printf("New window requested -> %s \n", uri);
234 new_window_load_uri(uri);
239 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
243 if (uzbl.state.selected_url != NULL) {
244 if (uzbl.state.verbose)
245 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
246 new_window_load_uri(uzbl.state.selected_url);
248 if (uzbl.state.verbose)
249 printf("New web view -> %s\n","Nothing to open, exiting");
255 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
258 if (uzbl.behave.download_handler) {
259 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
260 if (uzbl.state.verbose)
261 printf("Download -> %s\n",uri);
262 /* if urls not escaped, we may have to escape and quote uri before this call */
263 run_handler(uzbl.behave.download_handler, uri);
268 /* scroll a bar in a given direction */
270 scroll (GtkAdjustment* bar, GArray *argv) {
274 amount = g_ascii_strtod(g_array_index(argv, gchar*, 0), &end);
275 if (*end == '%') amount = gtk_adjustment_get_page_size(bar) * amount * 0.01;
276 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
279 static void scroll_begin(WebKitWebView* page, GArray *argv) {
280 (void) page; (void) argv;
281 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
284 static void scroll_end(WebKitWebView* page, GArray *argv) {
285 (void) page; (void) argv;
286 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
287 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
290 static void scroll_vert(WebKitWebView* page, GArray *argv) {
292 scroll(uzbl.gui.bar_v, argv);
295 static void scroll_horz(WebKitWebView* page, GArray *argv) {
297 scroll(uzbl.gui.bar_h, argv);
302 if (!uzbl.behave.show_status) {
303 gtk_widget_hide(uzbl.gui.mainbar);
305 gtk_widget_show(uzbl.gui.mainbar);
311 toggle_status_cb (WebKitWebView* page, GArray *argv) {
315 if (uzbl.behave.show_status) {
316 gtk_widget_hide(uzbl.gui.mainbar);
318 gtk_widget_show(uzbl.gui.mainbar);
320 uzbl.behave.show_status = !uzbl.behave.show_status;
325 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
329 //Set selected_url state variable
330 g_free(uzbl.state.selected_url);
331 uzbl.state.selected_url = NULL;
333 uzbl.state.selected_url = g_strdup(link);
339 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
343 if (uzbl.gui.main_title)
344 g_free (uzbl.gui.main_title);
345 uzbl.gui.main_title = g_strdup (title);
350 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
353 uzbl.gui.sbar.load_progress = progress;
358 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
362 if (uzbl.behave.load_finish_handler)
363 run_handler(uzbl.behave.load_finish_handler, "");
367 load_start_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
371 if (uzbl.behave.load_start_handler)
372 run_handler(uzbl.behave.load_start_handler, "");
376 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
379 free (uzbl.state.uri);
380 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
381 uzbl.state.uri = g_string_free (newuri, FALSE);
382 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
383 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
386 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
387 if (uzbl.behave.load_commit_handler)
388 run_handler(uzbl.behave.load_commit_handler, uzbl.state.uri);
392 destroy_cb (GtkWidget* widget, gpointer data) {
400 if (uzbl.behave.history_handler) {
402 struct tm * timeinfo;
405 timeinfo = localtime ( &rawtime );
406 strftime (date, 80, "\"%Y-%m-%d %H:%M:%S\"", timeinfo);
407 run_handler(uzbl.behave.history_handler, date);
412 /* VIEW funcs (little webkit wrappers) */
413 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, GArray *argv){(void)argv; webkit_web_view_##name(page);}
415 VIEWFUNC(reload_bypass_cache)
416 VIEWFUNC(stop_loading)
423 /* -- command to callback/function map for things we cannot attach to any signals */
426 static struct {char *name; Command command[2];} cmdlist[] =
427 { /* key function no_split */
428 { "back", {view_go_back, 0} },
429 { "forward", {view_go_forward, 0} },
430 { "scroll_vert", {scroll_vert, 0} },
431 { "scroll_horz", {scroll_horz, 0} },
432 { "scroll_begin", {scroll_begin, 0} },
433 { "scroll_end", {scroll_end, 0} },
434 { "reload", {view_reload, 0}, },
435 { "reload_ign_cache", {view_reload_bypass_cache, 0} },
436 { "stop", {view_stop_loading, 0}, },
437 { "zoom_in", {view_zoom_in, 0}, }, //Can crash (when max zoom reached?).
438 { "zoom_out", {view_zoom_out, 0}, },
439 { "uri", {load_uri, NOSPLIT} },
440 { "script", {run_js, NOSPLIT} },
441 { "toggle_status", {toggle_status_cb, 0} },
442 { "spawn", {spawn, 0} },
443 { "sh", {spawn_sh, 0} },
444 { "exit", {close_uzbl, 0} },
445 { "search", {search_forward_text, NOSPLIT} },
446 { "search_reverse", {search_reverse_text, NOSPLIT} },
447 { "insert_mode", {set_insert_mode, 0} },
448 { "runcmd", {runcmd, NOSPLIT} }
455 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
457 for (i = 0; i < LENGTH(cmdlist); i++)
458 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
461 /* -- CORE FUNCTIONS -- */
464 free_action(gpointer act) {
465 Action *action = (Action*)act;
466 g_free(action->name);
468 g_free(action->param);
473 new_action(const gchar *name, const gchar *param) {
474 Action *action = g_new(Action, 1);
476 action->name = g_strdup(name);
478 action->param = g_strdup(param);
480 action->param = NULL;
486 file_exists (const char * filename) {
487 return (access(filename, F_OK) == 0);
491 set_insert_mode(WebKitWebView *page, GArray *argv) {
495 uzbl.behave.insert_mode = TRUE;
500 load_uri (WebKitWebView *web_view, GArray *argv) {
501 if (argv_idx(argv, 0)) {
502 GString* newuri = g_string_new (argv_idx(argv, 0));
503 if (g_strrstr (argv_idx(argv, 0), "://") == NULL)
504 g_string_prepend (newuri, "http://");
505 /* if we do handle cookies, ask our handler for them */
506 webkit_web_view_load_uri (web_view, newuri->str);
507 g_string_free (newuri, TRUE);
512 run_js (WebKitWebView * web_view, GArray *argv) {
513 if (argv_idx(argv, 0))
514 webkit_web_view_execute_script (web_view, argv_idx(argv, 0));
518 search_text (WebKitWebView *page, GArray *argv, const gboolean forward) {
519 if (argv_idx(argv, 0) && (*argv_idx(argv, 0) != '\0'))
520 uzbl.state.searchtx = g_strdup(argv_idx(argv, 0));
522 if (uzbl.state.searchtx != NULL) {
523 if (uzbl.state.verbose)
524 printf ("Searching: %s\n", uzbl.state.searchtx);
525 webkit_web_view_unmark_text_matches (page);
526 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
527 webkit_web_view_set_highlight_text_matches (page, TRUE);
528 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, forward, TRUE);
529 g_free(uzbl.state.searchtx);
530 uzbl.state.searchtx = NULL;
535 search_forward_text (WebKitWebView *page, GArray *argv) {
536 search_text(page, argv, TRUE);
540 search_reverse_text (WebKitWebView *page, GArray *argv) {
541 search_text(page, argv, FALSE);
545 new_window_load_uri (const gchar * uri) {
546 GString* to_execute = g_string_new ("");
547 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
549 for (i = 0; entries[i].long_name != NULL; i++) {
550 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0) && (strcmp(entries[i].long_name,"name")!=0)) {
551 gchar** str = (gchar**)entries[i].arg_data;
553 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
557 if (uzbl.state.verbose)
558 printf("\n%s\n", to_execute->str);
559 g_spawn_command_line_async (to_execute->str, NULL);
560 g_string_free (to_execute, TRUE);
564 close_uzbl (WebKitWebView *page, GArray *argv) {
570 /* --Statusbar functions-- */
572 build_progressbar_ascii(int percent) {
576 GString *bar = g_string_new("");
578 l = (double)percent*((double)width/100.);
579 l = (int)(l+.5)>=(int)l ? l+.5 : l;
581 for(i=0; i<(int)l; i++)
582 g_string_append(bar, "=");
585 g_string_append(bar, "·");
587 return g_string_free(bar, FALSE);
592 const GScannerConfig scan_config = {
595 ) /* cset_skip_characters */,
600 ) /* cset_identifier_first */,
607 ) /* cset_identifier_nth */,
608 ( "" ) /* cpair_comment_single */,
610 TRUE /* case_sensitive */,
612 FALSE /* skip_comment_multi */,
613 FALSE /* skip_comment_single */,
614 FALSE /* scan_comment_multi */,
615 TRUE /* scan_identifier */,
616 TRUE /* scan_identifier_1char */,
617 FALSE /* scan_identifier_NULL */,
618 TRUE /* scan_symbols */,
619 FALSE /* scan_binary */,
620 FALSE /* scan_octal */,
621 FALSE /* scan_float */,
622 FALSE /* scan_hex */,
623 FALSE /* scan_hex_dollar */,
624 FALSE /* scan_string_sq */,
625 FALSE /* scan_string_dq */,
626 TRUE /* numbers_2_int */,
627 FALSE /* int_2_float */,
628 FALSE /* identifier_2_string */,
629 FALSE /* char_2_token */,
630 FALSE /* symbol_2_token */,
631 TRUE /* scope_0_fallback */,
636 uzbl.scan = g_scanner_new(&scan_config);
637 while(symp->symbol_name) {
638 g_scanner_scope_add_symbol(uzbl.scan, 0,
640 GINT_TO_POINTER(symp->symbol_token));
646 expand_template(const char *template) {
647 if(!template) return NULL;
649 GTokenType token = G_TOKEN_NONE;
650 GString *ret = g_string_new("");
654 g_scanner_input_text(uzbl.scan, template, strlen(template));
655 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
656 token = g_scanner_get_next_token(uzbl.scan);
658 if(token == G_TOKEN_SYMBOL) {
659 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
662 buf = uzbl.state.uri?
663 g_markup_printf_escaped("%s", uzbl.state.uri) :
665 g_string_append(ret, buf);
669 buf = itos(uzbl.gui.sbar.load_progress);
670 g_string_append(ret, buf);
673 case SYM_LOADPRGSBAR:
674 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
675 g_string_append(ret, buf);
679 buf = uzbl.gui.main_title?
680 g_markup_printf_escaped("%s", uzbl.gui.main_title) :
682 g_string_append(ret, buf);
685 case SYM_SELECTED_URI:
686 buf = uzbl.state.selected_url?
687 g_markup_printf_escaped("%s", uzbl.state.selected_url) :
689 g_string_append(ret, buf);
693 buf = itos(uzbl.xwin);
695 uzbl.state.instance_name?uzbl.state.instance_name:buf);
699 buf = uzbl.state.keycmd->str?
700 g_markup_printf_escaped("%s", uzbl.state.keycmd->str) :
702 g_string_append(ret, buf);
707 uzbl.behave.insert_mode?"[I]":"[C]");
711 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
715 buf = itos(WEBKIT_MAJOR_VERSION);
716 g_string_append(ret, buf);
720 buf = itos(WEBKIT_MINOR_VERSION);
721 g_string_append(ret, buf);
725 buf = itos(WEBKIT_MICRO_VERSION);
726 g_string_append(ret, buf);
730 g_string_append(ret, uzbl.state.unameinfo.sysname);
733 g_string_append(ret, uzbl.state.unameinfo.nodename);
736 g_string_append(ret, uzbl.state.unameinfo.release);
739 g_string_append(ret, uzbl.state.unameinfo.version);
742 g_string_append(ret, uzbl.state.unameinfo.machine);
745 g_string_append(ret, ARCH);
749 g_string_append(ret, uzbl.state.unameinfo.domainname);
753 g_string_append(ret, COMMIT);
759 else if(token == G_TOKEN_INT) {
760 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
761 g_string_append(ret, buf);
764 else if(token == G_TOKEN_IDENTIFIER) {
765 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
767 else if(token == G_TOKEN_CHAR) {
768 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
772 return g_string_free(ret, FALSE);
774 /* --End Statusbar functions-- */
777 sharg_append(GArray *a, const gchar *str) {
778 const gchar *s = (str ? str : "");
779 g_array_append_val(a, s);
782 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
784 run_command (const gchar *command, const guint npre, const gchar **args,
785 const gboolean sync, char **stdout) {
786 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
789 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
790 gchar *pid = itos(getpid());
791 gchar *xwin = itos(uzbl.xwin);
793 sharg_append(a, command);
794 for (i = 0; i < npre; i++) /* add n args before the default vars */
795 sharg_append(a, args[i]);
796 sharg_append(a, uzbl.state.config_file);
797 sharg_append(a, pid);
798 sharg_append(a, xwin);
799 sharg_append(a, uzbl.comm.fifo_path);
800 sharg_append(a, uzbl.comm.socket_path);
801 sharg_append(a, uzbl.state.uri);
802 sharg_append(a, uzbl.gui.main_title);
804 for (i = npre; i < g_strv_length((gchar**)args); i++)
805 sharg_append(a, args[i]);
808 if (sync) result = g_spawn_sync(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
809 NULL, NULL, stdout, NULL, NULL, &err);
810 else result = g_spawn_async(NULL, (gchar **)a->data, NULL, G_SPAWN_SEARCH_PATH,
811 NULL, NULL, NULL, &err);
813 if (uzbl.state.verbose) {
814 GString *s = g_string_new("spawned:");
815 for (i = 0; i < (a->len); i++) {
816 gchar *qarg = g_shell_quote(g_array_index(a, gchar*, i));
817 g_string_append_printf(s, " %s", qarg);
820 g_string_append_printf(s, " -- result: %s", (result ? "true" : "false"));
821 printf("%s\n", s->str);
822 g_string_free(s, TRUE);
825 g_printerr("error on run_command: %s\n", err->message);
830 g_array_free (a, TRUE);
835 split_quoted(const gchar* src, const gboolean unquote) {
836 /* split on unquoted space, return array of strings;
837 remove a layer of quotes and backslashes if unquote */
838 if (!src) return NULL;
842 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
843 GString *s = g_string_new ("");
847 for (p = src; *p != '\0'; p++) {
848 if ((*p == '\\') && unquote) g_string_append_c(s, *++p);
849 else if (*p == '\\') { g_string_append_c(s, *p++);
850 g_string_append_c(s, *p); }
851 else if ((*p == '"') && unquote && !sq) dq = !dq;
852 else if (*p == '"' && !sq) { g_string_append_c(s, *p);
854 else if ((*p == '\'') && unquote && !dq) sq = !sq;
855 else if (*p == '\'' && !dq) { g_string_append_c(s, *p);
857 else if ((*p == ' ') && !dq && !sq) {
858 dup = g_strdup(s->str);
859 g_array_append_val(a, dup);
860 g_string_truncate(s, 0);
861 } else g_string_append_c(s, *p);
863 dup = g_strdup(s->str);
864 g_array_append_val(a, dup);
865 ret = (gchar**)a->data;
866 g_array_free (a, FALSE);
867 g_string_free (s, FALSE);
872 spawn(WebKitWebView *web_view, GArray *argv) {
874 //TODO: allow more control over argument order so that users can have some arguments before the default ones from run_command, and some after
875 if (argv_idx(argv, 0)) run_command(argv_idx(argv, 0), 0, argv->data + sizeof(gchar*), FALSE, NULL);
879 spawn_sh(WebKitWebView *web_view, GArray *argv) {
881 if (!uzbl.behave.shell_cmd) {
882 g_printerr ("spawn_sh: shell_cmd is not set!\n");
887 gchar *spacer = g_strdup("");
888 g_array_insert_val(argv, 1, spacer);
889 gchar **cmd = split_quoted(uzbl.behave.shell_cmd, TRUE);
891 for (i = 1; i < g_strv_length(cmd); i++)
892 g_array_prepend_val(argv, cmd[i]);
894 if (cmd) run_command(cmd[0], g_strv_length(cmd) + 1, argv->data, FALSE, NULL);
900 parse_command(const char *cmd, const char *param) {
903 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd))) {
906 gchar **par = split_quoted(param, TRUE);
907 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
909 if (c[1] == NOSPLIT) { /* don't split */
910 sharg_append(a, param);
912 for (i = 0; i < g_strv_length(par); i++)
913 sharg_append(a, par[i]);
915 c[0](uzbl.gui.web_view, a);
917 g_array_free (a, TRUE);
920 g_printerr ("command \"%s\" not understood. ignoring.\n", cmd);
926 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
927 G_REGEX_OPTIMIZE, 0, NULL);
928 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
929 G_REGEX_OPTIMIZE, 0, NULL);
930 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
931 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
932 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
933 G_REGEX_OPTIMIZE, 0, NULL);
934 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
935 G_REGEX_OPTIMIZE, 0, NULL);
939 get_var_value(gchar *name) {
942 if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
943 if(c->type == TYPE_STRING)
944 printf("VAR: %s VALUE: %s\n", name, (char *)*c->ptr);
945 else if(c->type == TYPE_INT)
946 printf("VAR: %s VALUE: %d\n", name, (int)*c->ptr);
955 if(*uzbl.net.proxy_url == ' '
956 || uzbl.net.proxy_url == NULL) {
957 soup_session_remove_feature_by_type(uzbl.net.soup_session,
958 (GType) SOUP_SESSION_PROXY_URI);
961 suri = soup_uri_new(uzbl.net.proxy_url);
962 g_object_set(G_OBJECT(uzbl.net.soup_session),
963 SOUP_SESSION_PROXY_URI,
972 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
973 g_array_append_val (a, uzbl.state.uri);
974 load_uri(uzbl.gui.web_view, a);
975 g_array_free (a, TRUE);
979 cmd_always_insert_mode() {
980 uzbl.behave.insert_mode =
981 uzbl.behave.always_insert_mode ? TRUE : FALSE;
987 g_object_set(G_OBJECT(uzbl.net.soup_session),
988 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
992 cmd_max_conns_host() {
993 g_object_set(G_OBJECT(uzbl.net.soup_session),
994 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
999 soup_session_remove_feature
1000 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1001 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
1002 /*g_free(uzbl.net.soup_logger);*/
1004 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
1005 soup_session_add_feature(uzbl.net.soup_session,
1006 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
1010 cmd_default_font_size() {
1011 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1012 g_object_set (G_OBJECT(ws), "default-font-size", uzbl.behave.default_font_size, NULL);
1016 cmd_minimum_font_size() {
1017 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
1018 g_object_set (G_OBJECT(ws), "minimum-font-size", uzbl.behave.minimum_font_size, NULL);
1025 buf = init_fifo(uzbl.behave.fifo_dir);
1026 if(uzbl.behave.fifo_dir)
1027 free(uzbl.behave.fifo_dir);
1029 uzbl.behave.fifo_dir = buf?buf:g_strdup("");
1036 buf = init_socket(uzbl.behave.fifo_dir);
1037 if(uzbl.behave.socket_dir)
1038 free(uzbl.behave.socket_dir);
1040 uzbl.behave.socket_dir = buf?buf:g_strdup("");
1048 buf = g_utf8_strup(uzbl.behave.modkey, -1);
1049 uzbl.behave.modmask = 0;
1051 if(uzbl.behave.modkey)
1052 free(uzbl.behave.modkey);
1053 uzbl.behave.modkey = buf;
1055 for (i = 0; modkeys[i].key != NULL; i++) {
1056 if (g_strrstr(uzbl.behave.modkey, modkeys[i].key))
1057 uzbl.behave.modmask |= modkeys[i].mask;
1065 buf = set_useragent(uzbl.net.useragent);
1066 if(uzbl.net.useragent)
1067 free(uzbl.net.useragent);
1069 uzbl.net.useragent = buf?buf:g_strdup("");
1074 gtk_widget_ref(uzbl.gui.scrolled_win);
1075 gtk_widget_ref(uzbl.gui.mainbar);
1076 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
1077 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
1079 if(uzbl.behave.status_top) {
1080 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1081 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1084 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1085 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1087 gtk_widget_unref(uzbl.gui.scrolled_win);
1088 gtk_widget_unref(uzbl.gui.mainbar);
1089 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1094 set_var_value(gchar *name, gchar *val) {
1095 uzbl_cmdprop *c = NULL;
1098 if( (c = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
1099 /* check for the variable type */
1100 if (c->type == TYPE_STRING) {
1102 *c->ptr = g_strdup(val);
1103 } else if(c->type == TYPE_INT) {
1104 int *ip = GPOINTER_TO_INT(c->ptr);
1105 *ip = (int)strtoul(val, &endp, 10);
1108 /* invoke a command specific function */
1109 if(c->func) c->func();
1115 runcmd(WebKitWebView* page, GArray *argv) {
1117 parse_cmd_line(argv_idx(argv, 0));
1121 parse_cmd_line(const char *ctl_line) {
1125 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
1126 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
1127 if(tokens[0][0] == 0) {
1128 set_var_value(tokens[1], tokens[2]);
1132 printf("Error in command: %s\n", tokens[0]);
1135 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
1136 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
1137 if(tokens[0][0] == 0) {
1138 get_var_value(tokens[1]);
1142 printf("Error in command: %s\n", tokens[0]);
1145 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1146 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1147 if(tokens[0][0] == 0) {
1148 add_binding(tokens[1], tokens[2]);
1152 printf("Error in command: %s\n", tokens[0]);
1155 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1156 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1157 if(tokens[0][0] == 0) {
1158 parse_command(tokens[1], tokens[2]);
1162 printf("Error in command: %s\n", tokens[0]);
1164 /* KEYCMD command */
1165 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1166 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1167 if(tokens[0][0] == 0) {
1168 /* should incremental commands want each individual "keystroke"
1169 sent in a loop or the whole string in one go like now? */
1170 g_string_assign(uzbl.state.keycmd, tokens[1]);
1172 if (g_strstr_len(ctl_line, 7, "n") || g_strstr_len(ctl_line, 7, "N"))
1179 else if( (ctl_line[0] == '#')
1180 || (ctl_line[0] == ' ')
1181 || (ctl_line[0] == '\n'))
1182 ; /* ignore these lines */
1184 printf("Command not understood (%s)\n", ctl_line);
1190 build_stream_name(int type, const gchar* dir) {
1192 State *s = &uzbl.state;
1195 xwin_str = itos((int)uzbl.xwin);
1197 str = g_strdup_printf
1198 ("%s/uzbl_fifo_%s", dir,
1199 s->instance_name ? s->instance_name : xwin_str);
1200 } else if (type == SOCKET) {
1201 str = g_strdup_printf
1202 ("%s/uzbl_socket_%s", dir,
1203 s->instance_name ? s->instance_name : xwin_str );
1210 control_fifo(GIOChannel *gio, GIOCondition condition) {
1211 if (uzbl.state.verbose)
1212 printf("triggered\n");
1217 if (condition & G_IO_HUP)
1218 g_error ("Fifo: Read end of pipe died!\n");
1221 g_error ("Fifo: GIOChannel broke\n");
1223 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1224 if (ret == G_IO_STATUS_ERROR) {
1225 g_error ("Fifo: Error reading: %s\n", err->message);
1229 parse_cmd_line(ctl_line);
1236 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1237 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1238 if (unlink(uzbl.comm.fifo_path) == -1)
1239 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1240 g_free(uzbl.comm.fifo_path);
1241 uzbl.comm.fifo_path = NULL;
1244 if (*dir == ' ') { /* space unsets the variable */
1248 GIOChannel *chan = NULL;
1249 GError *error = NULL;
1250 gchar *path = build_stream_name(FIFO, dir);
1252 if (!file_exists(path)) {
1253 if (mkfifo (path, 0666) == 0) {
1254 // 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.
1255 chan = g_io_channel_new_file(path, "r+", &error);
1257 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1258 if (uzbl.state.verbose)
1259 printf ("init_fifo: created successfully as %s\n", path);
1260 uzbl.comm.fifo_path = path;
1262 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1263 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1264 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1265 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1267 /* if we got this far, there was an error; cleanup */
1268 if (error) g_error_free (error);
1274 control_stdin(GIOChannel *gio, GIOCondition condition) {
1276 gchar *ctl_line = NULL;
1279 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, NULL);
1280 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1283 parse_cmd_line(ctl_line);
1291 GIOChannel *chan = NULL;
1292 GError *error = NULL;
1294 chan = g_io_channel_unix_new(fileno(stdin));
1296 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1297 g_error ("Stdin: could not add watch\n");
1299 if (uzbl.state.verbose)
1300 printf ("Stdin: watch added successfully\n");
1303 g_error ("Stdin: Error while opening: %s\n", error->message);
1305 if (error) g_error_free (error);
1309 control_socket(GIOChannel *chan) {
1310 struct sockaddr_un remote;
1311 char buffer[512], *ctl_line;
1313 int sock, clientsock, n, done;
1316 sock = g_io_channel_unix_get_fd(chan);
1318 memset (buffer, 0, sizeof (buffer));
1320 t = sizeof (remote);
1321 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1325 memset (temp, 0, sizeof (temp));
1326 n = recv (clientsock, temp, 128, 0);
1328 buffer[strlen (buffer)] = '\0';
1332 strcat (buffer, temp);
1335 if (strcmp (buffer, "\n") < 0) {
1336 buffer[strlen (buffer) - 1] = '\0';
1338 buffer[strlen (buffer)] = '\0';
1341 ctl_line = g_strdup(buffer);
1342 parse_cmd_line (ctl_line);
1345 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1346 GError *error = NULL;
1349 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1350 if (ret == G_IO_STATUS_ERROR)
1351 g_error ("Error reading: %s\n", error->message);
1353 printf("Got line %s (%u bytes) \n",ctl_line, len);
1355 parse_line(ctl_line);
1363 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1364 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1365 if (unlink(uzbl.comm.socket_path) == -1)
1366 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1367 g_free(uzbl.comm.socket_path);
1368 uzbl.comm.socket_path = NULL;
1376 GIOChannel *chan = NULL;
1378 struct sockaddr_un local;
1379 gchar *path = build_stream_name(SOCKET, dir);
1381 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1383 local.sun_family = AF_UNIX;
1384 strcpy (local.sun_path, path);
1385 unlink (local.sun_path);
1387 len = strlen (local.sun_path) + sizeof (local.sun_family);
1388 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1389 if (uzbl.state.verbose)
1390 printf ("init_socket: opened in %s\n", path);
1393 if( (chan = g_io_channel_unix_new(sock)) ) {
1394 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1395 uzbl.comm.socket_path = path;
1398 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1400 /* if we got this far, there was an error; cleanup */
1407 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1408 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1410 // this function may be called very early when the templates are not set (yet), hence the checks
1412 update_title (void) {
1413 Behaviour *b = &uzbl.behave;
1416 if (b->show_status) {
1417 if (b->title_format_short) {
1418 parsed = expand_template(b->title_format_short);
1419 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1422 if (b->status_format) {
1423 parsed = expand_template(b->status_format);
1424 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1427 if (b->status_background) {
1429 gdk_color_parse (b->status_background, &color);
1430 //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)
1431 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1434 if (b->title_format_long) {
1435 parsed = expand_template(b->title_format_long);
1436 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1443 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1445 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1449 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1450 || 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)
1453 /* turn off insert mode (if always_insert_mode is not used) */
1454 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1455 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1460 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1463 if (event->keyval == GDK_Escape) {
1464 g_string_truncate(uzbl.state.keycmd, 0);
1469 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1470 if (event->keyval == GDK_Insert) {
1472 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1473 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1475 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1478 g_string_append (uzbl.state.keycmd, str);
1485 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1486 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1490 gboolean key_ret = FALSE;
1491 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1493 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1495 run_keycmd(key_ret);
1497 if (key_ret) return (!uzbl.behave.insert_mode);
1502 run_keycmd(const gboolean key_ret) {
1503 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1505 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1506 g_string_truncate(uzbl.state.keycmd, 0);
1507 parse_command(action->name, action->param);
1511 /* try if it's an incremental keycmd or one that takes args, and run it */
1512 GString* short_keys = g_string_new ("");
1513 GString* short_keys_inc = g_string_new ("");
1515 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1516 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1517 g_string_assign(short_keys_inc, short_keys->str);
1518 g_string_append_c(short_keys, '_');
1519 g_string_append_c(short_keys_inc, '*');
1521 gboolean exec_now = FALSE;
1522 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1523 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1524 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1525 if (key_ret) { /* just quit the incremental command on return */
1526 g_string_truncate(uzbl.state.keycmd, 0);
1528 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1532 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1533 GString* actionname = g_string_new ("");
1534 GString* actionparam = g_string_new ("");
1535 g_string_erase (parampart, 0, i+1);
1537 g_string_printf (actionname, action->name, parampart->str);
1539 g_string_printf (actionparam, action->param, parampart->str);
1540 parse_command(actionname->str, actionparam->str);
1541 g_string_free (actionname, TRUE);
1542 g_string_free (actionparam, TRUE);
1543 g_string_free (parampart, TRUE);
1545 g_string_truncate(uzbl.state.keycmd, 0);
1549 g_string_truncate(short_keys, short_keys->len - 1);
1551 g_string_free (short_keys, TRUE);
1552 g_string_free (short_keys_inc, TRUE);
1559 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1560 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
1562 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1563 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1565 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1566 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1567 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1568 g_signal_connect (G_OBJECT (g->web_view), "load-started", G_CALLBACK (load_start_cb), g->web_view);
1569 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1570 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1571 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1572 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1573 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1574 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1575 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1577 return scrolled_window;
1584 g->mainbar = gtk_hbox_new (FALSE, 0);
1586 g->mainbar_label = gtk_label_new ("");
1587 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1588 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1589 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1590 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1591 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1596 GtkWidget* create_window () {
1597 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1598 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1599 gtk_widget_set_name (window, "Uzbl browser");
1600 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1606 run_handler (const gchar *act, const gchar *args) {
1607 char **parts = g_strsplit(act, " ", 2);
1609 else if ((g_strcmp0(parts[0], "spawn") == 0)
1610 || (g_strcmp0(parts[0], "sh") == 0)) {
1612 GString *a = g_string_new ("");
1614 spawnparts = split_quoted(parts[1], FALSE);
1615 g_string_append_printf(a, "%s", spawnparts[0]);
1616 if (args) g_string_append_printf(a, " %s", args); /* append handler args before user args */
1617 for (i = 1; i < g_strv_length(spawnparts); i++) /* user args */
1618 g_string_append_printf(a, " %s", spawnparts[i]);
1619 parse_command(parts[0], a->str);
1620 g_string_free (a, TRUE);
1621 g_strfreev (spawnparts);
1623 parse_command(parts[0], parts[1]);
1628 add_binding (const gchar *key, const gchar *act) {
1629 char **parts = g_strsplit(act, " ", 2);
1636 if (uzbl.state.verbose)
1637 printf ("Binding %-10s : %s\n", key, act);
1639 action = new_action(parts[0], parts[1]);
1640 g_hash_table_replace(uzbl.bindings, g_strdup(key), action);
1646 get_xdg_var (XDG_Var xdg) {
1647 const gchar* actual_value = getenv (xdg.environmental);
1648 const gchar* home = getenv ("HOME");
1650 gchar* return_value = str_replace ("~", home, actual_value);
1652 if (! actual_value || strcmp (actual_value, "") == 0) {
1653 if (xdg.default_value) {
1654 return_value = str_replace ("~", home, xdg.default_value);
1656 return_value = NULL;
1659 return return_value;
1663 find_xdg_file (int xdg_type, char* filename) {
1664 /* xdg_type = 0 => config
1665 xdg_type = 1 => data
1666 xdg_type = 2 => cache*/
1668 gchar* temporary_file = malloc (1024);
1669 gchar* temporary_string = NULL;
1673 buf = get_xdg_var (XDG[xdg_type]);
1674 strcpy (temporary_file, buf);
1675 strcat (temporary_file, filename);
1678 if (! file_exists (temporary_file) && xdg_type != 2) {
1679 buf = get_xdg_var (XDG[3 + xdg_type]);
1680 temporary_string = (char *) strtok_r (buf, ":", &saveptr);
1683 while (temporary_string && ! file_exists (temporary_file)) {
1684 strcpy (temporary_file, temporary_string);
1685 strcat (temporary_file, filename);
1686 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1690 if (file_exists (temporary_file)) {
1691 return temporary_file;
1699 State *s = &uzbl.state;
1700 Network *n = &uzbl.net;
1702 uzbl.behave.reset_command_mode = 1;
1704 if (!s->config_file) {
1705 s->config_file = find_xdg_file (0, "/uzbl/config");
1708 if (s->config_file) {
1709 GIOChannel *chan = NULL;
1710 gchar *readbuf = NULL;
1713 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1716 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1717 == G_IO_STATUS_NORMAL) {
1718 parse_cmd_line(readbuf);
1722 g_io_channel_unref (chan);
1723 if (uzbl.state.verbose)
1724 printf ("Config %s loaded\n", s->config_file);
1726 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1729 if (uzbl.state.verbose)
1730 printf ("No configuration file loaded.\n");
1732 if (!uzbl.behave.status_format)
1733 set_var_value("status_format", STATUS_DEFAULT);
1734 if (!uzbl.behave.title_format_long)
1735 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1736 if (!uzbl.behave.title_format_short)
1737 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1740 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1744 set_useragent(gchar *val) {
1749 gchar *ua = expand_template(val);
1751 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1755 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1758 if (!uzbl.behave.cookie_handler) return;
1760 gchar * stdout = NULL;
1761 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1762 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1763 gchar *action = g_strdup ("GET");
1764 SoupURI * soup_uri = soup_message_get_uri(msg);
1765 sharg_append(a, action);
1766 sharg_append(a, soup_uri->host);
1767 sharg_append(a, soup_uri->path);
1768 run_command(uzbl.behave.cookie_handler, 0, a->data, TRUE, &stdout); /* TODO: use handler */
1769 //run_handler(uzbl.behave.cookie_handler); /* TODO: global stdout pointer, spawn_sync */
1771 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1774 g_array_free(a, TRUE);
1778 save_cookies (SoupMessage *msg, gpointer user_data){
1782 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1783 cookie = soup_cookie_to_set_cookie_header(ck->data);
1784 GArray *a = g_array_new(TRUE, FALSE, sizeof(gchar*));
1785 SoupURI * soup_uri = soup_message_get_uri(msg);
1786 gchar *action = strdup("PUT");
1787 sharg_append(a, action);
1788 sharg_append(a, soup_uri->host);
1789 sharg_append(a, soup_uri->path);
1790 sharg_append(a, cookie);
1791 run_command(uzbl.behave.cookie_handler, 0, a->data, FALSE, NULL);
1794 g_array_free(a, TRUE);
1801 main (int argc, char* argv[]) {
1802 gtk_init (&argc, &argv);
1803 if (!g_thread_supported ())
1804 g_thread_init (NULL);
1806 uzbl.state.executable_path = g_strdup(argv[0]);
1807 uzbl.state.selected_url = NULL;
1808 uzbl.state.searchtx = NULL;
1810 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1811 g_option_context_add_main_entries (context, entries, NULL);
1812 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1813 g_option_context_parse (context, &argc, &argv, NULL);
1814 g_option_context_free(context);
1815 /* initialize hash table */
1816 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1818 uzbl.net.soup_session = webkit_get_default_session();
1819 uzbl.state.keycmd = g_string_new("");
1821 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1822 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1823 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1824 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1826 if(uname(&uzbl.state.unameinfo) == -1)
1827 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1832 make_var_to_name_hash();
1835 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1837 uzbl.gui.scrolled_win = create_browser();
1840 /* initial packing */
1841 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1842 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1844 uzbl.gui.main_window = create_window ();
1845 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1848 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1849 gtk_widget_show_all (uzbl.gui.main_window);
1850 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1852 if (uzbl.state.verbose) {
1853 printf("Uzbl start location: %s\n", argv[0]);
1854 printf("window_id %i\n",(int) uzbl.xwin);
1855 printf("pid %i\n", getpid ());
1856 printf("name: %s\n", uzbl.state.instance_name);
1859 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1860 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1861 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1862 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1863 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1867 if (!uzbl.behave.show_status)
1868 gtk_widget_hide(uzbl.gui.mainbar);
1874 if(uzbl.state.uri) {
1875 GArray *a = g_array_new (TRUE, FALSE, sizeof(gchar*));
1876 g_array_append_val(a, uzbl.state.uri);
1877 load_uri (uzbl.gui.web_view, a);
1878 g_array_free (a, TRUE);
1884 return EXIT_SUCCESS;
1887 /* vi: set et ts=4: */