cleaner and more robust status/window format & update_title stuff
[uzbl-00z.git] / uzbl.c
blob8e04b6be6a7b81377c8ae91e1344152fad05ad9f
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.
6 /*
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
12 * are met:
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
36 #include <gtk/gtk.h>
37 #include <gdk/gdkx.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <sys/socket.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <sys/un.h>
43 #include <sys/utsname.h>
44 #include <webkit/webkit.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <stdlib.h>
49 #include <errno.h>
50 #include <string.h>
51 #include <fcntl.h>
52 #include <sys/socket.h>
53 #include <sys/un.h>
54 #include <libsoup/soup.h>
55 #include <signal.h>
56 #include "uzbl.h"
59 static Uzbl uzbl;
61 /* define names and pointers to all config specific variables */
62 const struct {
63 char *name;
64 void **ptr;
65 } var_name_to_ptr[] = {
66 { "uri", (void *)&uzbl.state.uri },
67 { "status_message", (void *)&uzbl.gui.sbar.msg },
68 { "show_status", (void *)&uzbl.behave.show_status },
69 { "status_top", (void *)&uzbl.behave.status_top },
70 { "status_format", (void *)&uzbl.behave.status_format },
71 { "status_background", (void *)&uzbl.behave.status_background },
72 { "title_format_long", (void *)&uzbl.behave.title_format_long },
73 { "title_format_short", (void *)&uzbl.behave.title_format_short },
74 { "insert_mode", (void *)&uzbl.behave.insert_mode },
75 { "always_insert_mode", (void *)&uzbl.behave.always_insert_mode },
76 { "reset_command_mode", (void *)&uzbl.behave.reset_command_mode },
77 { "modkey" , (void *)&uzbl.behave.modkey },
78 { "load_finish_handler",(void *)&uzbl.behave.load_finish_handler},
79 { "history_handler", (void *)&uzbl.behave.history_handler },
80 { "download_handler", (void *)&uzbl.behave.download_handler },
81 { "cookie_handler", (void *)&uzbl.behave.cookie_handler },
82 { "fifo_dir", (void *)&uzbl.behave.fifo_dir },
83 { "socket_dir", (void *)&uzbl.behave.socket_dir },
84 { "http_debug", (void *)&uzbl.behave.http_debug },
85 { "default_font_size", (void *)&uzbl.behave.default_font_size },
86 { "minimum_font_size", (void *)&uzbl.behave.minimum_font_size },
87 { "shell_cmd", (void *)&uzbl.behave.shell_cmd },
88 { "proxy_url", (void *)&uzbl.net.proxy_url },
89 { "max_conns", (void *)&uzbl.net.max_conns },
90 { "max_conns_host", (void *)&uzbl.net.max_conns_host },
91 { "useragent", (void *)&uzbl.net.useragent },
92 { NULL, NULL }
93 }, *n2v_p = var_name_to_ptr;
95 const struct {
96 char *key;
97 guint mask;
98 } modkeys[] = {
99 { "SHIFT", GDK_SHIFT_MASK }, // shift
100 { "LOCK", GDK_LOCK_MASK }, // capslock or shiftlock, depending on xserver's modmappings
101 { "CONTROL", GDK_CONTROL_MASK }, // control
102 { "MOD1", GDK_MOD1_MASK }, // 4th mod - normally alt but depends on modmappings
103 { "MOD2", GDK_MOD2_MASK }, // 5th mod
104 { "MOD3", GDK_MOD3_MASK }, // 6th mod
105 { "MOD4", GDK_MOD4_MASK }, // 7th mod
106 { "MOD5", GDK_MOD5_MASK }, // 8th mod
107 { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
108 { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
109 { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
110 { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
111 { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
112 { "SUPER", GDK_SUPER_MASK }, // super (since 2.10)
113 { "HYPER", GDK_HYPER_MASK }, // hyper (since 2.10)
114 { "META", GDK_META_MASK }, // meta (since 2.10)
115 { NULL, 0 }
118 /* construct a hash from the var_name_to_ptr array for quick access */
119 static void
120 make_var_to_name_hash() {
121 uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
122 while(n2v_p->name) {
123 g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, n2v_p->ptr);
124 n2v_p++;
128 /* commandline arguments (set initial values for the state variables) */
129 static GOptionEntry entries[] =
131 { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, "Uri to load at startup (equivalent to 'set uri = URI')", "URI" },
132 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, "Whether to print all messages or just errors.", "VERBOSE" },
133 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
134 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file (this is pretty much equivalent to uzbl < FILE )", "FILE" },
135 { NULL, 0, 0, 0, NULL, NULL, NULL }
138 typedef void (*Command)(WebKitWebView*, const char *);
140 /* --- UTILITY FUNCTIONS --- */
142 char *
143 itos(int val) {
144 char tmp[20];
146 snprintf(tmp, sizeof(tmp), "%i", val);
147 return g_strdup(tmp);
150 static char *
151 str_replace (const char* search, const char* replace, const char* string) {
152 return g_strjoinv (replace, g_strsplit (string, search, -1));
155 static sigfunc*
156 setup_signal(int signr, sigfunc *shandler) {
157 struct sigaction nh, oh;
159 nh.sa_handler = shandler;
160 sigemptyset(&nh.sa_mask);
161 nh.sa_flags = 0;
163 if(sigaction(signr, &nh, &oh) < 0)
164 return SIG_ERR;
166 return NULL;
169 static void
170 clean_up(void) {
171 if (uzbl.behave.fifo_dir)
172 unlink (uzbl.comm.fifo_path);
173 if (uzbl.behave.socket_dir)
174 unlink (uzbl.comm.socket_path);
176 g_string_free(uzbl.state.keycmd, TRUE);
177 g_hash_table_destroy(uzbl.bindings);
178 g_hash_table_destroy(uzbl.behave.commands);
182 /* --- SIGNAL HANDLER --- */
184 static void
185 catch_sigterm(int s) {
186 (void) s;
187 clean_up();
190 static void
191 catch_sigint(int s) {
192 (void) s;
193 clean_up();
194 exit(EXIT_SUCCESS);
197 /* --- CALLBACKS --- */
199 static gboolean
200 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
201 (void) web_view;
202 (void) frame;
203 (void) navigation_action;
204 (void) policy_decision;
205 (void) user_data;
206 const gchar* uri = webkit_network_request_get_uri (request);
207 if (uzbl.state.verbose)
208 printf("New window requested -> %s \n", uri);
209 new_window_load_uri(uri);
210 return (FALSE);
213 WebKitWebView*
214 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
215 (void) web_view;
216 (void) frame;
217 (void) user_data;
218 if (uzbl.state.selected_url[0]!=0) {
219 if (uzbl.state.verbose)
220 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
221 new_window_load_uri(uzbl.state.selected_url);
222 } else {
223 if (uzbl.state.verbose)
224 printf("New web view -> %s\n","Nothing to open, exiting");
226 return (NULL);
229 static gboolean
230 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
231 (void) web_view;
232 (void) user_data;
233 if (uzbl.behave.download_handler) {
234 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
235 if (uzbl.state.verbose)
236 printf("Download -> %s\n",uri);
237 run_command(uzbl.behave.download_handler, uri, FALSE, NULL);
239 return (FALSE);
242 /* scroll a bar in a given direction */
243 static void
244 scroll (GtkAdjustment* bar, const char *param) {
245 gdouble amount;
246 gchar *end;
248 amount = g_ascii_strtod(param, &end);
250 if (*end)
251 fprintf(stderr, "found something after double: %s\n", end);
253 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
256 static void scroll_begin(WebKitWebView* page, const char *param) {
257 (void) page; (void) param;
258 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
261 static void scroll_end(WebKitWebView* page, const char *param) {
262 (void) page; (void) param;
263 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
264 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
267 static void scroll_vert(WebKitWebView* page, const char *param) {
268 (void) page;
269 scroll(uzbl.gui.bar_v, param);
272 static void scroll_horz(WebKitWebView* page, const char *param) {
273 (void) page;
274 scroll(uzbl.gui.bar_h, param);
277 static void
278 cmd_set_status() {
279 if (!uzbl.behave.show_status) {
280 gtk_widget_hide(uzbl.gui.mainbar);
281 } else {
282 gtk_widget_show(uzbl.gui.mainbar);
284 update_title();
287 static void
288 toggle_status_cb (WebKitWebView* page, const char *param) {
289 (void)page;
290 (void)param;
292 if (uzbl.behave.show_status) {
293 gtk_widget_hide(uzbl.gui.mainbar);
294 } else {
295 gtk_widget_show(uzbl.gui.mainbar);
297 uzbl.behave.show_status = !uzbl.behave.show_status;
298 update_title();
301 static void
302 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
303 (void) page;
304 (void) title;
305 (void) data;
306 //Set selected_url state variable
307 uzbl.state.selected_url[0] = '\0';
308 if (link) {
309 strcpy (uzbl.state.selected_url, link);
311 update_title();
314 static void
315 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
316 (void) web_view;
317 (void) web_frame;
318 (void) data;
319 if (uzbl.gui.main_title)
320 g_free (uzbl.gui.main_title);
321 uzbl.gui.main_title = g_strdup (title);
322 update_title();
325 static void
326 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
327 (void) page;
328 (void) data;
329 uzbl.gui.sbar.load_progress = progress;
330 update_title();
333 static void
334 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
335 (void) page;
336 (void) frame;
337 (void) data;
338 if (uzbl.behave.load_finish_handler) {
339 run_command(uzbl.behave.load_finish_handler, NULL, FALSE, NULL);
343 static void
344 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
345 (void) page;
346 (void) data;
347 free (uzbl.state.uri);
348 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
349 uzbl.state.uri = g_string_free (newuri, FALSE);
350 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
351 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
352 update_title();
354 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
357 static void
358 destroy_cb (GtkWidget* widget, gpointer data) {
359 (void) widget;
360 (void) data;
361 gtk_main_quit ();
364 static void
365 log_history_cb () {
366 if (uzbl.behave.history_handler) {
367 time_t rawtime;
368 struct tm * timeinfo;
369 char date [80];
370 time ( &rawtime );
371 timeinfo = localtime ( &rawtime );
372 strftime (date, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
373 GString* args = g_string_new ("");
374 g_string_printf (args, "'%s'", date);
375 run_command(uzbl.behave.history_handler, args->str, FALSE, NULL);
376 g_string_free (args, TRUE);
381 /* VIEW funcs (little webkit wrappers) */
382 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, const char *param){(void)param; webkit_web_view_##name(page);}
383 VIEWFUNC(reload)
384 VIEWFUNC(reload_bypass_cache)
385 VIEWFUNC(stop_loading)
386 VIEWFUNC(zoom_in)
387 VIEWFUNC(zoom_out)
388 VIEWFUNC(go_back)
389 VIEWFUNC(go_forward)
390 #undef VIEWFUNC
392 /* -- command to callback/function map for things we cannot attach to any signals */
393 // TODO: reload
395 static struct {char *name; Command command;} cmdlist[] =
397 { "back", view_go_back },
398 { "forward", view_go_forward },
399 { "scroll_vert", scroll_vert },
400 { "scroll_horz", scroll_horz },
401 { "scroll_begin", scroll_begin },
402 { "scroll_end", scroll_end },
403 { "reload", view_reload, },
404 { "reload_ign_cache", view_reload_bypass_cache},
405 { "stop", view_stop_loading, },
406 { "zoom_in", view_zoom_in, }, //Can crash (when max zoom reached?).
407 { "zoom_out", view_zoom_out, },
408 { "uri", load_uri },
409 { "script", run_js },
410 { "toggle_status", toggle_status_cb },
411 { "spawn", spawn },
412 { "sh", spawn_sh },
413 { "exit", close_uzbl },
414 { "search", search_text },
415 { "insert_mode", set_insert_mode },
416 { "runcmd", runcmd }
419 static void
420 commands_hash(void)
422 unsigned int i;
423 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
425 for (i = 0; i < LENGTH(cmdlist); i++)
426 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
429 /* -- CORE FUNCTIONS -- */
431 void
432 free_action(gpointer act) {
433 Action *action = (Action*)act;
434 g_free(action->name);
435 if (action->param)
436 g_free(action->param);
437 g_free(action);
440 Action*
441 new_action(const gchar *name, const gchar *param) {
442 Action *action = g_new(Action, 1);
444 action->name = g_strdup(name);
445 if (param)
446 action->param = g_strdup(param);
447 else
448 action->param = NULL;
450 return action;
453 static bool
454 file_exists (const char * filename) {
455 return (access(filename, F_OK) == 0);
458 void
459 set_insert_mode(WebKitWebView *page, const gchar *param) {
460 (void)page;
461 (void)param;
463 uzbl.behave.insert_mode = TRUE;
464 update_title();
467 static void
468 load_uri (WebKitWebView * web_view, const gchar *param) {
469 if (param) {
470 GString* newuri = g_string_new (param);
471 if (g_strrstr (param, "://") == NULL)
472 g_string_prepend (newuri, "http://");
473 /* if we do handle cookies, ask our handler for them */
474 webkit_web_view_load_uri (web_view, newuri->str);
475 g_string_free (newuri, TRUE);
479 static void
480 run_js (WebKitWebView * web_view, const gchar *param) {
481 if (param)
482 webkit_web_view_execute_script (web_view, param);
485 static void
486 search_text (WebKitWebView *page, const char *param) {
487 if ((param) && (param[0] != '\0')) {
488 strcpy(uzbl.state.searchtx, param);
490 if (uzbl.state.searchtx[0] != '\0') {
491 if (uzbl.state.verbose)
492 printf ("Searching: %s\n", uzbl.state.searchtx);
493 webkit_web_view_unmark_text_matches (page);
494 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
495 webkit_web_view_set_highlight_text_matches (page, TRUE);
496 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, TRUE, TRUE);
500 static void
501 new_window_load_uri (const gchar * uri) {
502 GString* to_execute = g_string_new ("");
503 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
504 int i;
505 for (i = 0; entries[i].long_name != NULL; i++) {
506 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0)) {
507 gchar** str = (gchar**)entries[i].arg_data;
508 if (*str!=NULL) {
509 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
513 if (uzbl.state.verbose)
514 printf("\n%s\n", to_execute->str);
515 g_spawn_command_line_async (to_execute->str, NULL);
516 g_string_free (to_execute, TRUE);
519 static void
520 close_uzbl (WebKitWebView *page, const char *param) {
521 (void)page;
522 (void)param;
523 gtk_main_quit ();
526 /* --Statusbar functions-- */
527 static char*
528 build_progressbar_ascii(int percent) {
529 int width=10;
530 int i;
531 double l;
532 GString *bar = g_string_new("");
534 l = (double)percent*((double)width/100.);
535 l = (int)(l+.5)>=(int)l ? l+.5 : l;
537 for(i=0; i<(int)l; i++)
538 g_string_append(bar, "=");
540 for(; i<width; i++)
541 g_string_append(bar, "·");
543 return g_string_free(bar, FALSE);
546 static void
547 setup_scanner() {
548 const GScannerConfig scan_config = {
550 "\t\r\n"
551 ) /* cset_skip_characters */,
553 G_CSET_a_2_z
554 "_#"
555 G_CSET_A_2_Z
556 ) /* cset_identifier_first */,
558 G_CSET_a_2_z
559 "_0123456789"
560 G_CSET_A_2_Z
561 G_CSET_LATINS
562 G_CSET_LATINC
563 ) /* cset_identifier_nth */,
564 ( "" ) /* cpair_comment_single */,
566 TRUE /* case_sensitive */,
568 FALSE /* skip_comment_multi */,
569 FALSE /* skip_comment_single */,
570 FALSE /* scan_comment_multi */,
571 TRUE /* scan_identifier */,
572 TRUE /* scan_identifier_1char */,
573 FALSE /* scan_identifier_NULL */,
574 TRUE /* scan_symbols */,
575 FALSE /* scan_binary */,
576 FALSE /* scan_octal */,
577 FALSE /* scan_float */,
578 FALSE /* scan_hex */,
579 FALSE /* scan_hex_dollar */,
580 FALSE /* scan_string_sq */,
581 FALSE /* scan_string_dq */,
582 TRUE /* numbers_2_int */,
583 FALSE /* int_2_float */,
584 FALSE /* identifier_2_string */,
585 FALSE /* char_2_token */,
586 FALSE /* symbol_2_token */,
587 TRUE /* scope_0_fallback */,
588 FALSE,
589 TRUE
592 uzbl.scan = g_scanner_new(&scan_config);
593 while(symp->symbol_name) {
594 g_scanner_scope_add_symbol(uzbl.scan, 0,
595 symp->symbol_name,
596 GINT_TO_POINTER(symp->symbol_token));
597 symp++;
601 static gchar *
602 expand_template(const char *template) {
603 if(!template) return NULL;
605 GTokenType token = G_TOKEN_NONE;
606 GString *ret = g_string_new("");
607 char *buf=NULL;
608 int sym;
610 g_scanner_input_text(uzbl.scan, template, strlen(template));
611 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
612 token = g_scanner_get_next_token(uzbl.scan);
614 if(token == G_TOKEN_SYMBOL) {
615 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
616 switch(sym) {
617 case SYM_URI:
618 g_string_append(ret,
619 uzbl.state.uri?
620 g_markup_printf_escaped("%s", uzbl.state.uri):"");
621 break;
622 case SYM_LOADPRGS:
623 buf = itos(uzbl.gui.sbar.load_progress);
624 g_string_append(ret, buf);
625 free(buf);
626 break;
627 case SYM_LOADPRGSBAR:
628 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
629 g_string_append(ret, buf);
630 g_free(buf);
631 break;
632 case SYM_TITLE:
633 g_string_append(ret,
634 uzbl.gui.main_title?
635 g_markup_printf_escaped("%s", uzbl.gui.main_title):"");
636 break;
637 case SYM_SELECTED_URI:
638 g_string_append(ret,
639 uzbl.state.selected_url?
640 g_markup_printf_escaped("%s", uzbl.state.selected_url):"");
641 break;
642 case SYM_NAME:
643 buf = itos(uzbl.xwin);
644 g_string_append(ret,
645 uzbl.state.instance_name?uzbl.state.instance_name:buf);
646 free(buf);
647 break;
648 case SYM_KEYCMD:
649 g_string_append(ret,
650 uzbl.state.keycmd->str ?
651 g_markup_printf_escaped("%s", uzbl.state.keycmd->str):"");
652 break;
653 case SYM_MODE:
654 g_string_append(ret,
655 uzbl.behave.insert_mode?"[I]":"[C]");
656 break;
657 case SYM_MSG:
658 g_string_append(ret,
659 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
660 break;
661 /* useragent syms */
662 case SYM_WK_MAJ:
663 buf = itos(WEBKIT_MAJOR_VERSION);
664 g_string_append(ret, buf);
665 free(buf);
666 break;
667 case SYM_WK_MIN:
668 buf = itos(WEBKIT_MINOR_VERSION);
669 g_string_append(ret, buf);
670 free(buf);
671 break;
672 case SYM_WK_MIC:
673 buf = itos(WEBKIT_MICRO_VERSION);
674 g_string_append(ret, buf);
675 free(buf);
676 break;
677 case SYM_SYSNAME:
678 g_string_append(ret, uzbl.state.unameinfo.sysname);
679 break;
680 case SYM_NODENAME:
681 g_string_append(ret, uzbl.state.unameinfo.nodename);
682 break;
683 case SYM_KERNREL:
684 g_string_append(ret, uzbl.state.unameinfo.release);
685 break;
686 case SYM_KERNVER:
687 g_string_append(ret, uzbl.state.unameinfo.version);
688 break;
689 case SYM_ARCHSYS:
690 g_string_append(ret, uzbl.state.unameinfo.machine);
691 break;
692 case SYM_ARCHUZBL:
693 g_string_append(ret, ARCH);
694 break;
695 #ifdef _GNU_SOURCE
696 case SYM_DOMAINNAME:
697 g_string_append(ret, uzbl.state.unameinfo.domainname);
698 break;
699 #endif
700 case SYM_COMMIT:
701 g_string_append(ret, COMMIT);
702 break;
703 default:
704 break;
707 else if(token == G_TOKEN_INT) {
708 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
709 g_string_append(ret, buf);
710 free(buf);
712 else if(token == G_TOKEN_IDENTIFIER) {
713 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
715 else if(token == G_TOKEN_CHAR) {
716 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
720 return g_string_free(ret, FALSE);
722 /* --End Statusbar functions-- */
725 // make sure that the args string you pass can properly be interpreted (eg properly escaped against whitespace, quotes etc)
726 static gboolean
727 run_command (const char *command, const char *args, const gboolean sync, char **stdout) {
728 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
729 GString *to_execute = g_string_new ("");
730 GError *err = NULL;
731 gchar *cmd = g_strstrip(g_strdup(command));
732 gchar *qcfg = (uzbl.state.config_file ? g_shell_quote(uzbl.state.config_file) : g_strdup("''"));
733 gchar *qfifo = (uzbl.comm.fifo_path ? g_shell_quote(uzbl.comm.fifo_path) : g_strdup("''"));
734 gchar *qsock = (uzbl.comm.socket_path ? g_shell_quote(uzbl.comm.socket_path) : g_strdup("''"));
735 gchar *quri = (uzbl.state.uri ? g_shell_quote(uzbl.state.uri) : g_strdup("''"));
736 gchar *qtitle = (uzbl.gui.main_title ? g_shell_quote(uzbl.gui.main_title) : g_strdup("''"));
738 gboolean result;
739 g_string_printf (to_execute, "%s %s '%i' '%i' %s %s",
740 cmd, qcfg, (int) getpid(), (int) uzbl.xwin, qfifo, qsock);
741 g_string_append_printf (to_execute, " %s %s", quri, qtitle);
742 if(args) g_string_append_printf (to_execute, " %s", args);
744 if (sync) {
745 result = g_spawn_command_line_sync (to_execute->str, stdout, NULL, NULL, &err);
746 } else result = g_spawn_command_line_async (to_execute->str, &err);
747 if (uzbl.state.verbose)
748 printf("Called %s. Result: %s\n", to_execute->str, (result ? "TRUE" : "FALSE" ));
749 g_string_free (to_execute, TRUE);
750 if (err) {
751 g_printerr("error on run_command: %s\n", err->message);
752 g_error_free (err);
755 g_free (qcfg);
756 g_free (qfifo);
757 g_free (qsock);
758 g_free (quri);
759 g_free (qtitle);
760 g_free (cmd);
761 return result;
764 static void
765 spawn(WebKitWebView *web_view, const char *param) {
766 (void)web_view;
767 run_command(param, NULL, FALSE, NULL);
770 static void
771 spawn_sh(WebKitWebView *web_view, const char *param) {
772 (void)web_view;
773 gchar *cmd = g_strdup_printf(uzbl.behave.shell_cmd, param);
774 spawn(NULL, cmd);
775 g_free(cmd);
778 static void
779 parse_command(const char *cmd, const char *param) {
780 Command c;
782 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd)))
783 c(uzbl.gui.web_view, param);
784 else
785 fprintf (stderr, "command \"%s\" not understood. ignoring.\n", cmd);
788 /* command parser */
789 static void
790 setup_regex() {
791 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
792 G_REGEX_OPTIMIZE, 0, NULL);
793 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
794 G_REGEX_OPTIMIZE, 0, NULL);
795 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
796 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, NULL);
797 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
798 G_REGEX_OPTIMIZE, 0, NULL);
799 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
800 G_REGEX_OPTIMIZE, 0, NULL);
803 static gboolean
804 get_var_value(gchar *name) {
805 void **p = NULL;
807 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
808 if(var_is("status_format", name)
809 || var_is("useragent", name)
810 || var_is("title_format_short", name)
811 || var_is("title_format_long", name)) {
812 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
813 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
815 return TRUE;
818 static void
819 set_proxy_url() {
820 SoupURI *suri;
822 if(*uzbl.net.proxy_url == ' '
823 || uzbl.net.proxy_url == NULL) {
824 soup_session_remove_feature_by_type(uzbl.net.soup_session,
825 (GType) SOUP_SESSION_PROXY_URI);
827 else {
828 suri = soup_uri_new(uzbl.net.proxy_url);
829 g_object_set(G_OBJECT(uzbl.net.soup_session),
830 SOUP_SESSION_PROXY_URI,
831 suri, NULL);
832 soup_uri_free(suri);
834 return;
838 static void
839 move_statusbar() {
840 gtk_widget_ref(uzbl.gui.scrolled_win);
841 gtk_widget_ref(uzbl.gui.mainbar);
842 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
843 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
845 if(uzbl.behave.status_top) {
846 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
847 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
849 else {
850 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
851 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
853 gtk_widget_unref(uzbl.gui.scrolled_win);
854 gtk_widget_unref(uzbl.gui.mainbar);
855 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
858 static gboolean
859 var_is(const char *x, const char *y) {
860 return (strcmp(x, y) == 0 ? TRUE : FALSE );
863 static gboolean
864 set_var_value(gchar *name, gchar *val) {
865 void **p = NULL;
866 char *endp = NULL;
868 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
869 if(var_is("status_message", name)
870 || var_is("status_background", name)
871 || var_is("status_format", name)
872 || var_is("title_format_long", name)
873 || var_is("title_format_short", name)
874 || var_is("load_finish_handler", name)
875 || var_is("history_handler", name)
876 || var_is("download_handler", name)
877 || var_is("cookie_handler", name)) {
878 if(*p)
879 free(*p);
880 *p = g_strdup(val);
881 update_title();
883 else if(var_is("uri", name)) {
884 if(*p) free(*p);
885 *p = g_strdup(val);
886 load_uri(uzbl.gui.web_view, (const gchar*)*p);
888 else if(var_is("proxy_url", name)) {
889 if(*p) free(*p);
890 *p = g_strdup(val);
891 set_proxy_url();
893 else if(var_is("fifo_dir", name)) {
894 if(*p) free(*p);
895 *p = init_fifo(g_strdup(val));
897 else if(var_is("socket_dir", name)) {
898 if(*p) free(*p);
899 *p = init_socket(g_strdup(val));
901 else if(var_is("modkey", name)) {
902 if(*p) free(*p);
903 int i;
904 *p = g_utf8_strup(val, -1);
905 uzbl.behave.modmask = 0;
906 for (i = 0; modkeys[i].key != NULL; i++) {
907 if (g_strrstr(*p, modkeys[i].key))
908 uzbl.behave.modmask |= modkeys[i].mask;
911 else if(var_is("useragent", name)) {
912 if(*p) free(*p);
913 *p = set_useragent(g_strdup(val));
915 else if(var_is("shell_cmd", name)) {
916 if(*p) free(*p);
917 *p = g_strdup(val);
919 /* variables that take int values */
920 else {
921 int *ip = (int *)p;
922 *ip = (int)strtoul(val, &endp, 10);
924 if(var_is("show_status", name)) {
925 cmd_set_status();
927 else if(var_is("always_insert_mode", name)) {
928 uzbl.behave.insert_mode =
929 uzbl.behave.always_insert_mode ? TRUE : FALSE;
930 update_title();
932 else if (var_is("max_conns", name)) {
933 g_object_set(G_OBJECT(uzbl.net.soup_session),
934 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
936 else if (var_is("max_conns_host", name)) {
937 g_object_set(G_OBJECT(uzbl.net.soup_session),
938 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
940 else if (var_is("http_debug", name)) {
941 //soup_session_remove_feature
942 // (uzbl.net.soup_session, uzbl.net.soup_logger);
943 soup_session_remove_feature
944 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
945 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
946 /*g_free(uzbl.net.soup_logger);*/
948 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
949 soup_session_add_feature(uzbl.net.soup_session,
950 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
952 else if (var_is("status_top", name)) {
953 move_statusbar();
955 else if (var_is("default_font_size", name)) {
956 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
957 g_object_set (G_OBJECT(ws), "default-font-size", *ip, NULL);
959 else if (var_is("minimum_font_size", name)) {
960 WebKitWebSettings *ws = webkit_web_view_get_settings(uzbl.gui.web_view);
961 g_object_set (G_OBJECT(ws), "minimum-font-size", *ip, NULL);
965 return TRUE;
968 static void
969 runcmd(WebKitWebView* page, const char *param) {
970 (void) page;
971 parse_cmd_line(param);
974 static void
975 parse_cmd_line(const char *ctl_line) {
976 gchar **tokens;
978 /* SET command */
979 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
980 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
981 if(tokens[0][0] == 0) {
982 set_var_value(tokens[1], tokens[2]);
983 g_strfreev(tokens);
985 else
986 printf("Error in command: %s\n", tokens[0]);
988 /* GET command */
989 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
990 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
991 if(tokens[0][0] == 0) {
992 get_var_value(tokens[1]);
993 g_strfreev(tokens);
995 else
996 printf("Error in command: %s\n", tokens[0]);
998 /* BIND command */
999 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
1000 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
1001 if(tokens[0][0] == 0) {
1002 add_binding(tokens[1], tokens[2]);
1003 g_strfreev(tokens);
1005 else
1006 printf("Error in command: %s\n", tokens[0]);
1008 /* ACT command */
1009 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
1010 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
1011 if(tokens[0][0] == 0) {
1012 parse_command(tokens[1], tokens[2]);
1013 g_strfreev(tokens);
1015 else
1016 printf("Error in command: %s\n", tokens[0]);
1018 /* KEYCMD command */
1019 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
1020 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
1021 if(tokens[0][0] == 0) {
1022 /* should incremental commands want each individual "keystroke"
1023 sent in a loop or the whole string in one go like now? */
1024 g_string_assign(uzbl.state.keycmd, tokens[1]);
1025 run_keycmd(FALSE);
1026 update_title();
1027 g_strfreev(tokens);
1030 /* Comments */
1031 else if( (ctl_line[0] == '#')
1032 || (ctl_line[0] == ' ')
1033 || (ctl_line[0] == '\n'))
1034 ; /* ignore these lines */
1035 else
1036 printf("Command not understood (%s)\n", ctl_line);
1038 return;
1041 static gchar*
1042 build_stream_name(int type, const gchar* dir) {
1043 char *xwin_str;
1044 State *s = &uzbl.state;
1045 gchar *str;
1047 xwin_str = itos((int)uzbl.xwin);
1048 if (type == FIFO) {
1049 str = g_strdup_printf
1050 ("%s/uzbl_fifo_%s", dir,
1051 s->instance_name ? s->instance_name : xwin_str);
1052 } else if (type == SOCKET) {
1053 str = g_strdup_printf
1054 ("%s/uzbl_socket_%s", dir,
1055 s->instance_name ? s->instance_name : xwin_str );
1057 g_free(xwin_str);
1058 return str;
1061 static gboolean
1062 control_fifo(GIOChannel *gio, GIOCondition condition) {
1063 if (uzbl.state.verbose)
1064 printf("triggered\n");
1065 gchar *ctl_line;
1066 GIOStatus ret;
1067 GError *err = NULL;
1069 if (condition & G_IO_HUP)
1070 g_error ("Fifo: Read end of pipe died!\n");
1072 if(!gio)
1073 g_error ("Fifo: GIOChannel broke\n");
1075 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1076 if (ret == G_IO_STATUS_ERROR) {
1077 g_error ("Fifo: Error reading: %s\n", err->message);
1078 g_error_free (err);
1081 parse_cmd_line(ctl_line);
1082 g_free(ctl_line);
1084 return TRUE;
1087 static gchar*
1088 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1089 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1090 if (unlink(uzbl.comm.fifo_path) == -1)
1091 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1092 g_free(uzbl.comm.fifo_path);
1093 uzbl.comm.fifo_path = NULL;
1096 if (*dir == ' ') { /* space unsets the variable */
1097 g_free(dir);
1098 return NULL;
1101 GIOChannel *chan = NULL;
1102 GError *error = NULL;
1103 gchar *path = build_stream_name(FIFO, dir);
1105 if (!file_exists(path)) {
1106 if (mkfifo (path, 0666) == 0) {
1107 // 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.
1108 chan = g_io_channel_new_file(path, "r+", &error);
1109 if (chan) {
1110 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1111 if (uzbl.state.verbose)
1112 printf ("init_fifo: created successfully as %s\n", path);
1113 uzbl.comm.fifo_path = path;
1114 return dir;
1115 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1116 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1117 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1118 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1120 /* if we got this far, there was an error; cleanup */
1121 if (error) g_error_free (error);
1122 g_free(path);
1123 g_free(dir);
1124 return NULL;
1127 static gboolean
1128 control_stdin(GIOChannel *gio, GIOCondition condition) {
1129 gchar *ctl_line = NULL;
1130 gsize ctl_line_len = 0;
1131 GIOStatus ret;
1133 if (condition & G_IO_HUP) {
1134 ret = g_io_channel_shutdown (gio, FALSE, NULL);
1135 return FALSE;
1138 ret = g_io_channel_read_line(gio, &ctl_line, &ctl_line_len, NULL, NULL);
1139 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1140 return FALSE;
1142 parse_cmd_line(ctl_line);
1143 g_free(ctl_line);
1145 return TRUE;
1148 static void
1149 create_stdin () {
1150 GIOChannel *chan = NULL;
1151 GError *error = NULL;
1153 chan = g_io_channel_unix_new(fileno(stdin));
1154 if (chan) {
1155 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1156 g_error ("Stdin: could not add watch\n");
1157 } else {
1158 if (uzbl.state.verbose)
1159 printf ("Stdin: watch added successfully\n");
1161 } else {
1162 g_error ("Stdin: Error while opening: %s\n", error->message);
1164 if (error) g_error_free (error);
1167 static gboolean
1168 control_socket(GIOChannel *chan) {
1169 struct sockaddr_un remote;
1170 char buffer[512], *ctl_line;
1171 char temp[128];
1172 int sock, clientsock, n, done;
1173 unsigned int t;
1175 sock = g_io_channel_unix_get_fd(chan);
1177 memset (buffer, 0, sizeof (buffer));
1179 t = sizeof (remote);
1180 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1182 done = 0;
1183 do {
1184 memset (temp, 0, sizeof (temp));
1185 n = recv (clientsock, temp, 128, 0);
1186 if (n == 0) {
1187 buffer[strlen (buffer)] = '\0';
1188 done = 1;
1190 if (!done)
1191 strcat (buffer, temp);
1192 } while (!done);
1194 if (strcmp (buffer, "\n") < 0) {
1195 buffer[strlen (buffer) - 1] = '\0';
1196 } else {
1197 buffer[strlen (buffer)] = '\0';
1199 close (clientsock);
1200 ctl_line = g_strdup(buffer);
1201 parse_cmd_line (ctl_line);
1204 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1205 GError *error = NULL;
1206 gsize len;
1207 GIOStatus ret;
1208 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1209 if (ret == G_IO_STATUS_ERROR)
1210 g_error ("Error reading: %s\n", error->message);
1212 printf("Got line %s (%u bytes) \n",ctl_line, len);
1213 if(ctl_line) {
1214 parse_line(ctl_line);
1217 g_free(ctl_line);
1218 return TRUE;
1221 static gchar*
1222 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1223 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1224 if (unlink(uzbl.comm.socket_path) == -1)
1225 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1226 g_free(uzbl.comm.socket_path);
1227 uzbl.comm.socket_path = NULL;
1230 if (*dir == ' ') {
1231 g_free(dir);
1232 return NULL;
1235 GIOChannel *chan = NULL;
1236 int sock, len;
1237 struct sockaddr_un local;
1238 gchar *path = build_stream_name(SOCKET, dir);
1240 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1242 local.sun_family = AF_UNIX;
1243 strcpy (local.sun_path, path);
1244 unlink (local.sun_path);
1246 len = strlen (local.sun_path) + sizeof (local.sun_family);
1247 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1248 if (uzbl.state.verbose)
1249 printf ("init_socket: opened in %s\n", path);
1250 listen (sock, 5);
1252 if( (chan = g_io_channel_unix_new(sock)) ) {
1253 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1254 uzbl.comm.socket_path = path;
1255 return dir;
1257 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1259 /* if we got this far, there was an error; cleanup */
1260 g_free(path);
1261 g_free(dir);
1262 return NULL;
1266 NOTE: we want to keep variables like b->title_format_long in their "unprocessed" state
1267 it will probably improve performance if we would "cache" the processed variant, but for now it works well enough...
1269 // this function may be called very early when the templates are not set (yet), hence the checks
1270 static void
1271 update_title (void) {
1272 Behaviour *b = &uzbl.behave;
1273 gchar *parsed;
1275 if (b->show_status) {
1276 if (b->title_format_short) {
1277 parsed = expand_template(b->title_format_short);
1278 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1279 g_free(parsed);
1281 if (b->status_format) {
1282 parsed = expand_template(b->status_format);
1283 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), parsed);
1284 g_free(parsed);
1286 if (b->status_background) {
1287 GdkColor color;
1288 gdk_color_parse (b->status_background, &color);
1289 //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)
1290 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1292 } else {
1293 if (b->title_format_long) {
1294 parsed = expand_template(b->title_format_long);
1295 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), parsed);
1296 g_free(parsed);
1301 static gboolean
1302 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1304 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1306 (void) page;
1308 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1309 || 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)
1310 return FALSE;
1312 /* turn off insert mode (if always_insert_mode is not used) */
1313 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1314 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1315 update_title();
1316 return TRUE;
1319 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1320 return FALSE;
1322 if (event->keyval == GDK_Escape) {
1323 g_string_truncate(uzbl.state.keycmd, 0);
1324 update_title();
1325 return TRUE;
1328 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1329 if (event->keyval == GDK_Insert) {
1330 gchar * str;
1331 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1332 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1333 } else {
1334 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1336 if (str) {
1337 g_string_append (uzbl.state.keycmd, str);
1338 update_title ();
1339 free (str);
1341 return TRUE;
1344 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1345 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1346 update_title();
1349 gboolean key_ret = FALSE;
1350 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1351 key_ret = TRUE;
1352 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1354 run_keycmd(key_ret);
1355 update_title();
1356 if (key_ret) return (!uzbl.behave.insert_mode);
1357 return TRUE;
1360 static void
1361 run_keycmd(const gboolean key_ret) {
1362 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1363 Action *action;
1364 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1365 g_string_truncate(uzbl.state.keycmd, 0);
1366 parse_command(action->name, action->param);
1367 return;
1370 /* try if it's an incremental keycmd or one that takes args, and run it */
1371 GString* short_keys = g_string_new ("");
1372 GString* short_keys_inc = g_string_new ("");
1373 unsigned int i;
1374 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1375 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1376 g_string_assign(short_keys_inc, short_keys->str);
1377 g_string_append_c(short_keys, '_');
1378 g_string_append_c(short_keys_inc, '*');
1380 gboolean exec_now = FALSE;
1381 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1382 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1383 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1384 if (key_ret) { /* just quit the incremental command on return */
1385 g_string_truncate(uzbl.state.keycmd, 0);
1386 break;
1387 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1390 if (exec_now) {
1391 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1392 GString* actionname = g_string_new ("");
1393 GString* actionparam = g_string_new ("");
1394 g_string_erase (parampart, 0, i+1);
1395 if (action->name)
1396 g_string_printf (actionname, action->name, parampart->str);
1397 if (action->param)
1398 g_string_printf (actionparam, action->param, parampart->str);
1399 parse_command(actionname->str, actionparam->str);
1400 g_string_free (actionname, TRUE);
1401 g_string_free (actionparam, TRUE);
1402 g_string_free (parampart, TRUE);
1403 if (key_ret)
1404 g_string_truncate(uzbl.state.keycmd, 0);
1405 break;
1408 g_string_truncate(short_keys, short_keys->len - 1);
1410 g_string_free (short_keys, TRUE);
1411 g_string_free (short_keys_inc, TRUE);
1414 static GtkWidget*
1415 create_browser () {
1416 GUI *g = &uzbl.gui;
1418 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1419 //main_window_ref = g_object_ref(scrolled_window);
1420 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
1422 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1423 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1425 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1426 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1427 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1428 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1429 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1430 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1431 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1432 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1433 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1434 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1436 return scrolled_window;
1439 static GtkWidget*
1440 create_mainbar () {
1441 GUI *g = &uzbl.gui;
1443 g->mainbar = gtk_hbox_new (FALSE, 0);
1445 /* keep a reference to the bar so we can re-pack it at runtime*/
1446 //sbar_ref = g_object_ref(g->mainbar);
1448 g->mainbar_label = gtk_label_new ("");
1449 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1450 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1451 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1452 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1453 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1454 return g->mainbar;
1457 static
1458 GtkWidget* create_window () {
1459 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1460 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1461 gtk_widget_set_name (window, "Uzbl browser");
1462 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1464 return window;
1467 static void
1468 add_binding (const gchar *key, const gchar *act) {
1469 char **parts = g_strsplit(act, " ", 2);
1470 Action *action;
1472 if (!parts)
1473 return;
1475 //Debug:
1476 if (uzbl.state.verbose)
1477 printf ("Binding %-10s : %s\n", key, act);
1478 action = new_action(parts[0], parts[1]);
1480 if(g_hash_table_lookup(uzbl.bindings, key))
1481 g_hash_table_remove(uzbl.bindings, key);
1482 g_hash_table_insert(uzbl.bindings, g_strdup(key), action);
1484 g_strfreev(parts);
1487 static gchar*
1488 get_xdg_var (XDG_Var xdg) {
1489 const gchar* actual_value = getenv (xdg.environmental);
1490 const gchar* home = getenv ("HOME");
1492 gchar* return_value = str_replace ("~", home, g_strdup (actual_value));
1494 if (! actual_value || strcmp (actual_value, "") == 0) {
1495 if (xdg.default_value) {
1496 return_value = str_replace ("~", home, g_strdup (xdg.default_value));
1497 } else {
1498 return_value = NULL;
1502 return return_value;
1505 static gchar*
1506 find_xdg_file (int xdg_type, char* filename) {
1507 /* xdg_type = 0 => config
1508 xdg_type = 1 => data
1509 xdg_type = 2 => cache*/
1511 gchar* xdg_config_home = get_xdg_var (XDG[0]);
1512 gchar* xdg_data_home = get_xdg_var (XDG[1]);
1513 gchar* xdg_cache_home = get_xdg_var (XDG[2]);
1514 gchar* xdg_config_dirs = get_xdg_var (XDG[3]);
1515 gchar* xdg_data_dirs = get_xdg_var (XDG[4]);
1516 gchar* temporary_file = (char *)malloc (1024);
1517 gchar* temporary_string = NULL;
1518 char* saveptr;
1520 if (xdg_type == 0)
1521 strcpy (temporary_file, xdg_config_home);
1523 if (xdg_type == 1)
1524 strcpy (temporary_file, xdg_data_home);
1526 if (xdg_type == 2)
1527 strcpy (temporary_file, xdg_cache_home);
1529 strcat (temporary_file, filename);
1531 if (! file_exists (temporary_file) && xdg_type != 2) {
1532 if (xdg_type == 0)
1533 temporary_string = (char *) strtok_r (xdg_config_dirs, ":", &saveptr);
1535 if (xdg_type == 1)
1536 temporary_string = (char *) strtok_r (xdg_data_dirs, ":", &saveptr);
1538 while (temporary_string && ! file_exists (temporary_file)) {
1539 strcpy (temporary_file, temporary_string);
1540 strcat (temporary_file, filename);
1541 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1545 if (file_exists (temporary_file)) {
1546 return temporary_file;
1547 } else {
1548 return NULL;
1552 static void
1553 settings_init () {
1554 State *s = &uzbl.state;
1555 Network *n = &uzbl.net;
1557 uzbl.behave.reset_command_mode = 1;
1559 if (!s->config_file) {
1560 s->config_file = g_strdup (find_xdg_file (0, "/uzbl/config"));
1563 if (s->config_file) {
1564 GIOChannel *chan = NULL;
1565 gchar *readbuf = NULL;
1566 gsize len;
1568 chan = g_io_channel_new_file(s->config_file, "r", NULL);
1570 if (chan) {
1571 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1572 == G_IO_STATUS_NORMAL) {
1573 parse_cmd_line(readbuf);
1574 g_free (readbuf);
1577 g_io_channel_unref (chan);
1578 if (uzbl.state.verbose)
1579 printf ("Config %s loaded\n", s->config_file);
1580 } else {
1581 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1583 } else {
1584 if (uzbl.state.verbose)
1585 printf ("No configuration file loaded.\n");
1587 if (!uzbl.behave.status_format)
1588 set_var_value("status_format", STATUS_DEFAULT);
1589 if (!uzbl.behave.title_format_long)
1590 set_var_value("title_format_long", TITLE_LONG_DEFAULT);
1591 if (!uzbl.behave.title_format_short)
1592 set_var_value("title_format_short", TITLE_SHORT_DEFAULT);
1595 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1598 static gchar*
1599 set_useragent(gchar *val) {
1600 if (*val == ' ') {
1601 g_free(val);
1602 return NULL;
1604 gchar *ua = expand_template(val);
1605 if (ua)
1606 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1607 return ua;
1610 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1611 (void) session;
1612 (void) user_data;
1613 if (!uzbl.behave.cookie_handler) return;
1615 gchar * stdout = NULL;
1616 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1617 GString* args = g_string_new ("");
1618 SoupURI * soup_uri = soup_message_get_uri(msg);
1619 g_string_printf (args, "GET %s %s", soup_uri->host, soup_uri->path);
1620 run_command(uzbl.behave.cookie_handler, args->str, TRUE, &stdout);
1621 if(stdout) {
1622 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1624 g_string_free(args, TRUE);
1627 static void
1628 save_cookies (SoupMessage *msg, gpointer user_data){
1629 (void) user_data;
1630 GSList *ck;
1631 char *cookie;
1632 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1633 cookie = soup_cookie_to_set_cookie_header(ck->data);
1634 GString* args = g_string_new ("");
1635 SoupURI * soup_uri = soup_message_get_uri(msg);
1636 g_string_printf (args, "PUT %s %s \"%s\"", soup_uri->host, soup_uri->path, cookie);
1637 run_command(uzbl.behave.cookie_handler, args->str, FALSE, NULL);
1638 g_string_free(args, TRUE);
1639 free(cookie);
1641 g_slist_free(ck);
1646 main (int argc, char* argv[]) {
1647 gtk_init (&argc, &argv);
1648 if (!g_thread_supported ())
1649 g_thread_init (NULL);
1651 strcpy(uzbl.state.executable_path,argv[0]);
1653 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1654 g_option_context_add_main_entries (context, entries, NULL);
1655 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1656 g_option_context_parse (context, &argc, &argv, NULL);
1657 /* initialize hash table */
1658 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1660 uzbl.net.soup_session = webkit_get_default_session();
1661 uzbl.state.keycmd = g_string_new("");
1663 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1664 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1665 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1666 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1668 if(uname(&uzbl.state.unameinfo) == -1)
1669 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1671 setup_regex();
1672 setup_scanner();
1673 commands_hash ();
1674 make_var_to_name_hash();
1677 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1679 uzbl.gui.scrolled_win = create_browser();
1680 create_mainbar();
1682 /* initial packing */
1683 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1684 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1686 uzbl.gui.main_window = create_window ();
1687 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1689 load_uri (uzbl.gui.web_view, uzbl.state.uri); //TODO: is this needed?
1691 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1692 gtk_widget_show_all (uzbl.gui.main_window);
1693 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1695 if (uzbl.state.verbose) {
1696 printf("Uzbl start location: %s\n", argv[0]);
1697 printf("window_id %i\n",(int) uzbl.xwin);
1698 printf("pid %i\n", getpid ());
1699 printf("name: %s\n", uzbl.state.instance_name);
1702 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1703 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1704 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1705 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1706 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1708 settings_init ();
1710 if (!uzbl.behave.show_status)
1711 gtk_widget_hide(uzbl.gui.mainbar);
1712 else
1713 update_title();
1715 create_stdin();
1717 gtk_main ();
1718 clean_up();
1720 return EXIT_SUCCESS;
1723 /* vi: set et ts=4: */