Reimplemented my changes:
[uzbl-00z.git] / uzbl.c
blob18de4a89c27bdbd7be8b48aeb3770b79f275c52f
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 { "insert_mode", (void *)&uzbl.behave.insert_mode },
73 { "always_insert_mode", (void *)&uzbl.behave.always_insert_mode },
74 { "reset_command_mode", (void *)&uzbl.behave.reset_command_mode },
75 { "modkey" , (void *)&uzbl.behave.modkey },
76 { "load_finish_handler",(void *)&uzbl.behave.load_finish_handler},
77 { "history_handler", (void *)&uzbl.behave.history_handler },
78 { "download_handler", (void *)&uzbl.behave.download_handler },
79 { "cookie_handler", (void *)&uzbl.behave.cookie_handler },
80 { "fifo_dir", (void *)&uzbl.behave.fifo_dir },
81 { "socket_dir", (void *)&uzbl.behave.socket_dir },
82 { "http_debug", (void *)&uzbl.behave.http_debug },
83 { "proxy_url", (void *)&uzbl.net.proxy_url },
84 { "max_conns", (void *)&uzbl.net.max_conns },
85 { "max_conns_host", (void *)&uzbl.net.max_conns_host },
86 { "useragent", (void *)&uzbl.net.useragent },
87 { NULL, NULL }
88 }, *n2v_p = var_name_to_ptr;
90 const struct {
91 char *key;
92 guint mask;
93 } modkeys[] = {
94 { "SHIFT", GDK_SHIFT_MASK }, // shift
95 { "LOCK", GDK_LOCK_MASK }, // capslock or shiftlock, depending on xserver's modmappings
96 { "CONTROL", GDK_CONTROL_MASK }, // control
97 { "MOD1", GDK_MOD1_MASK }, // 4th mod - normally alt but depends on modmappings
98 { "MOD2", GDK_MOD2_MASK }, // 5th mod
99 { "MOD3", GDK_MOD3_MASK }, // 6th mod
100 { "MOD4", GDK_MOD4_MASK }, // 7th mod
101 { "MOD5", GDK_MOD5_MASK }, // 8th mod
102 { "BUTTON1", GDK_BUTTON1_MASK }, // 1st mouse button
103 { "BUTTON2", GDK_BUTTON2_MASK }, // 2nd mouse button
104 { "BUTTON3", GDK_BUTTON3_MASK }, // 3rd mouse button
105 { "BUTTON4", GDK_BUTTON4_MASK }, // 4th mouse button
106 { "BUTTON5", GDK_BUTTON5_MASK }, // 5th mouse button
107 { "SUPER", GDK_SUPER_MASK }, // super (since 2.10)
108 { "HYPER", GDK_HYPER_MASK }, // hyper (since 2.10)
109 { "META", GDK_META_MASK }, // meta (since 2.10)
110 { NULL, 0 }
113 /* construct a hash from the var_name_to_ptr array for quick access */
114 static void
115 make_var_to_name_hash() {
116 uzbl.comm.proto_var = g_hash_table_new(g_str_hash, g_str_equal);
117 while(n2v_p->name) {
118 g_hash_table_insert(uzbl.comm.proto_var, n2v_p->name, n2v_p->ptr);
119 n2v_p++;
123 /* commandline arguments (set initial values for the state variables) */
124 static GOptionEntry entries[] =
126 { "uri", 'u', 0, G_OPTION_ARG_STRING, &uzbl.state.uri, "Uri to load", "URI" },
127 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &uzbl.state.verbose, "Whether to print all messages or just errors.", "VERBOSE" },
128 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
129 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file", "FILE" },
130 { NULL, 0, 0, 0, NULL, NULL, NULL }
133 typedef void (*Command)(WebKitWebView*, const char *);
135 /* XDG stuff */
136 static char *XDG_CONFIG_HOME_default[256];
137 static char *XDG_CONFIG_DIRS_default = "/etc/xdg";
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 //ADD HOVER URL TO WINDOW TITLE
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 { "exit", close_uzbl },
413 { "search", search_text },
414 { "insert_mode", set_insert_mode },
415 { "runcmd", runcmd }
418 static void
419 commands_hash(void)
421 unsigned int i;
422 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
424 for (i = 0; i < LENGTH(cmdlist); i++)
425 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
428 /* -- CORE FUNCTIONS -- */
430 void
431 free_action(gpointer act) {
432 Action *action = (Action*)act;
433 g_free(action->name);
434 if (action->param)
435 g_free(action->param);
436 g_free(action);
439 Action*
440 new_action(const gchar *name, const gchar *param) {
441 Action *action = g_new(Action, 1);
443 action->name = g_strdup(name);
444 if (param)
445 action->param = g_strdup(param);
446 else
447 action->param = NULL;
449 return action;
452 static bool
453 file_exists (const char * filename) {
454 return (access(filename, F_OK) == 0);
457 void
458 set_insert_mode(WebKitWebView *page, const gchar *param) {
459 (void)page;
460 (void)param;
462 uzbl.behave.insert_mode = TRUE;
463 update_title();
466 static void
467 load_uri (WebKitWebView * web_view, const gchar *param) {
468 if (param) {
469 GString* newuri = g_string_new (param);
470 if (g_strrstr (param, "://") == NULL)
471 g_string_prepend (newuri, "http://");
472 /* if we do handle cookies, ask our handler for them */
473 webkit_web_view_load_uri (web_view, newuri->str);
474 g_string_free (newuri, TRUE);
478 static void
479 run_js (WebKitWebView * web_view, const gchar *param) {
480 if (param)
481 webkit_web_view_execute_script (web_view, param);
484 static void
485 search_text (WebKitWebView *page, const char *param) {
486 if ((param) && (param[0] != '\0')) {
487 strcpy(uzbl.state.searchtx, param);
489 if (uzbl.state.searchtx[0] != '\0') {
490 if (uzbl.state.verbose)
491 printf ("Searching: %s\n", uzbl.state.searchtx);
492 webkit_web_view_unmark_text_matches (page);
493 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
494 webkit_web_view_set_highlight_text_matches (page, TRUE);
495 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, TRUE, TRUE);
499 static void
500 new_window_load_uri (const gchar * uri) {
501 GString* to_execute = g_string_new ("");
502 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
503 int i;
504 for (i = 0; entries[i].long_name != NULL; i++) {
505 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0)) {
506 gchar** str = (gchar**)entries[i].arg_data;
507 if (*str!=NULL) {
508 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
512 if (uzbl.state.verbose)
513 printf("\n%s\n", to_execute->str);
514 g_spawn_command_line_async (to_execute->str, NULL);
515 g_string_free (to_execute, TRUE);
518 static void
519 close_uzbl (WebKitWebView *page, const char *param) {
520 (void)page;
521 (void)param;
522 gtk_main_quit ();
525 /* --Statusbar functions-- */
526 static char*
527 build_progressbar_ascii(int percent) {
528 int width=10;
529 int i;
530 double l;
531 GString *bar = g_string_new("");
533 l = (double)percent*((double)width/100.);
534 l = (int)(l+.5)>=(int)l ? l+.5 : l;
536 for(i=0; i<(int)l; i++)
537 g_string_append(bar, "=");
539 for(; i<width; i++)
540 g_string_append(bar, "·");
542 return g_string_free(bar, FALSE);
545 static void
546 setup_scanner() {
547 const GScannerConfig scan_config = {
549 "\t\r\n"
550 ) /* cset_skip_characters */,
552 G_CSET_a_2_z
553 "_#"
554 G_CSET_A_2_Z
555 ) /* cset_identifier_first */,
557 G_CSET_a_2_z
558 "_0123456789"
559 G_CSET_A_2_Z
560 G_CSET_LATINS
561 G_CSET_LATINC
562 ) /* cset_identifier_nth */,
563 ( "" ) /* cpair_comment_single */,
565 TRUE /* case_sensitive */,
567 FALSE /* skip_comment_multi */,
568 FALSE /* skip_comment_single */,
569 FALSE /* scan_comment_multi */,
570 TRUE /* scan_identifier */,
571 TRUE /* scan_identifier_1char */,
572 FALSE /* scan_identifier_NULL */,
573 TRUE /* scan_symbols */,
574 FALSE /* scan_binary */,
575 FALSE /* scan_octal */,
576 FALSE /* scan_float */,
577 FALSE /* scan_hex */,
578 FALSE /* scan_hex_dollar */,
579 FALSE /* scan_string_sq */,
580 FALSE /* scan_string_dq */,
581 TRUE /* numbers_2_int */,
582 FALSE /* int_2_float */,
583 FALSE /* identifier_2_string */,
584 FALSE /* char_2_token */,
585 FALSE /* symbol_2_token */,
586 TRUE /* scope_0_fallback */,
587 FALSE,
588 TRUE
591 uzbl.scan = g_scanner_new(&scan_config);
592 while(symp->symbol_name) {
593 g_scanner_scope_add_symbol(uzbl.scan, 0,
594 symp->symbol_name,
595 GINT_TO_POINTER(symp->symbol_token));
596 symp++;
600 static gchar *
601 expand_template(const char *template) {
602 if(!template) return NULL;
604 GTokenType token = G_TOKEN_NONE;
605 GString *ret = g_string_new("");
606 char *buf=NULL;
607 int sym;
609 g_scanner_input_text(uzbl.scan, template, strlen(template));
610 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
611 token = g_scanner_get_next_token(uzbl.scan);
613 if(token == G_TOKEN_SYMBOL) {
614 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
615 switch(sym) {
616 case SYM_URI:
617 g_string_append(ret,
618 uzbl.state.uri?
619 g_markup_printf_escaped("%s", uzbl.state.uri):"");
620 break;
621 case SYM_LOADPRGS:
622 buf = itos(uzbl.gui.sbar.load_progress);
623 g_string_append(ret, buf);
624 free(buf);
625 break;
626 case SYM_LOADPRGSBAR:
627 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
628 g_string_append(ret, buf);
629 g_free(buf);
630 break;
631 case SYM_TITLE:
632 g_string_append(ret,
633 uzbl.gui.main_title?
634 g_markup_printf_escaped("%s", uzbl.gui.main_title):"");
635 break;
636 case SYM_NAME:
637 buf = itos(uzbl.xwin);
638 g_string_append(ret,
639 uzbl.state.instance_name?uzbl.state.instance_name:buf);
640 free(buf);
641 break;
642 case SYM_KEYCMD:
643 g_string_append(ret,
644 uzbl.state.keycmd->str ?
645 g_markup_printf_escaped("%s", uzbl.state.keycmd->str):"");
646 break;
647 case SYM_MODE:
648 g_string_append(ret,
649 uzbl.behave.insert_mode?"[I]":"[C]");
650 break;
651 case SYM_MSG:
652 g_string_append(ret,
653 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
654 break;
655 /* useragent syms */
656 case SYM_WK_MAJ:
657 buf = itos(WEBKIT_MAJOR_VERSION);
658 g_string_append(ret, buf);
659 free(buf);
660 break;
661 case SYM_WK_MIN:
662 buf = itos(WEBKIT_MINOR_VERSION);
663 g_string_append(ret, buf);
664 free(buf);
665 break;
666 case SYM_WK_MIC:
667 buf = itos(WEBKIT_MICRO_VERSION);
668 g_string_append(ret, buf);
669 free(buf);
670 break;
671 case SYM_SYSNAME:
672 g_string_append(ret, uzbl.state.unameinfo.sysname);
673 break;
674 case SYM_NODENAME:
675 g_string_append(ret, uzbl.state.unameinfo.nodename);
676 break;
677 case SYM_KERNREL:
678 g_string_append(ret, uzbl.state.unameinfo.release);
679 break;
680 case SYM_KERNVER:
681 g_string_append(ret, uzbl.state.unameinfo.version);
682 break;
683 case SYM_ARCHSYS:
684 g_string_append(ret, uzbl.state.unameinfo.machine);
685 break;
686 case SYM_ARCHUZBL:
687 g_string_append(ret, ARCH);
688 break;
689 #ifdef _GNU_SOURCE
690 case SYM_DOMAINNAME:
691 g_string_append(ret, uzbl.state.unameinfo.domainname);
692 break;
693 #endif
694 case SYM_COMMIT:
695 g_string_append(ret, COMMIT);
696 break;
697 default:
698 break;
701 else if(token == G_TOKEN_INT) {
702 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
703 g_string_append(ret, buf);
704 free(buf);
706 else if(token == G_TOKEN_IDENTIFIER) {
707 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
709 else if(token == G_TOKEN_CHAR) {
710 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
714 return g_string_free(ret, FALSE);
716 /* --End Statusbar functions-- */
719 // make sure to put '' around args, so that if there is whitespace we can still keep arguments together.
720 static gboolean
721 run_command (const char *command, const char *args, const gboolean sync, char **stdout) {
722 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
723 GString* to_execute = g_string_new ("");
724 gboolean result;
725 g_string_printf (to_execute, "%s '%s' '%i' '%i' '%s' '%s'",
726 command, (uzbl.state.config_file ? uzbl.state.config_file : "(null)"),
727 (int) getpid(), (int) uzbl.xwin, uzbl.comm.fifo_path,
728 uzbl.comm.socket_path);
729 g_string_append_printf (to_execute, " '%s' '%s'",
730 uzbl.state.uri, uzbl.gui.main_title);
731 if(args) g_string_append_printf (to_execute, " %s", args);
733 if (sync) {
734 result = g_spawn_command_line_sync (to_execute->str, stdout, NULL, NULL, NULL);
735 } else result = g_spawn_command_line_async (to_execute->str, NULL);
736 printf("Called %s. Result: %s\n", to_execute->str, (result ? "TRUE" : "FALSE" ));
737 g_string_free (to_execute, TRUE);
738 return result;
741 static void
742 spawn(WebKitWebView *web_view, const char *param) {
743 (void)web_view;
744 run_command(param, NULL, FALSE, NULL);
747 static void
748 parse_command(const char *cmd, const char *param) {
749 Command c;
751 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd)))
752 c(uzbl.gui.web_view, param);
753 else
754 fprintf (stderr, "command \"%s\" not understood. ignoring.\n", cmd);
757 /* command parser */
758 static void
759 setup_regex() {
760 GError *err=NULL;
762 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
763 G_REGEX_OPTIMIZE, 0, &err);
764 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
765 G_REGEX_OPTIMIZE, 0, &err);
766 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
767 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, &err);
768 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
769 G_REGEX_OPTIMIZE, 0, &err);
770 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
771 G_REGEX_OPTIMIZE, 0, &err);
774 static gboolean
775 get_var_value(gchar *name) {
776 void **p = NULL;
778 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
779 if(var_is("status_format", name)
780 || var_is("useragent", name)) {
781 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
782 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
784 return TRUE;
787 static void
788 set_proxy_url() {
789 SoupURI *suri;
791 if(*uzbl.net.proxy_url == ' '
792 || uzbl.net.proxy_url == NULL) {
793 soup_session_remove_feature_by_type(uzbl.net.soup_session,
794 (GType) SOUP_SESSION_PROXY_URI);
796 else {
797 suri = soup_uri_new(uzbl.net.proxy_url);
798 g_object_set(G_OBJECT(uzbl.net.soup_session),
799 SOUP_SESSION_PROXY_URI,
800 suri, NULL);
801 soup_uri_free(suri);
803 return;
807 static void
808 move_statusbar() {
809 gtk_widget_ref(uzbl.gui.scrolled_win);
810 gtk_widget_ref(uzbl.gui.mainbar);
811 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
812 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
814 if(uzbl.behave.status_top) {
815 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
816 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
818 else {
819 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
820 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
822 gtk_widget_unref(uzbl.gui.scrolled_win);
823 gtk_widget_unref(uzbl.gui.mainbar);
824 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
827 static gboolean
828 var_is(const char *x, const char *y) {
829 return (strcmp(x, y) == 0 ? TRUE : FALSE );
832 static gboolean
833 set_var_value(gchar *name, gchar *val) {
834 void **p = NULL;
835 char *endp = NULL;
837 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
838 if(var_is("status_message", name)
839 || var_is("status_background", name)
840 || var_is("status_format", name)
841 || var_is("load_finish_handler", name)
842 || var_is("history_handler", name)
843 || var_is("download_handler", name)
844 || var_is("cookie_handler", name)) {
845 if(*p)
846 free(*p);
847 *p = g_strdup(val);
848 update_title();
850 else if(var_is("uri", name)) {
851 if(*p) free(*p);
852 *p = g_strdup(val);
853 load_uri(uzbl.gui.web_view, (const gchar*)*p);
855 else if(var_is("proxy_url", name)) {
856 if(*p) free(*p);
857 *p = g_strdup(val);
858 set_proxy_url();
860 else if(var_is("fifo_dir", name)) {
861 if(*p) free(*p);
862 *p = init_fifo(g_strdup(val));
864 else if(var_is("socket_dir", name)) {
865 if(*p) free(*p);
866 *p = init_socket(g_strdup(val));
868 else if(var_is("modkey", name)) {
869 if(*p) free(*p);
870 int i;
871 *p = g_utf8_strup(val, -1);
872 uzbl.behave.modmask = 0;
873 for (i = 0; modkeys[i].key != NULL; i++) {
874 if (g_strrstr(*p, modkeys[i].key))
875 uzbl.behave.modmask |= modkeys[i].mask;
878 else if(var_is("useragent", name)) {
879 if(*p) free(*p);
880 *p = set_useragent(g_strdup(val));
882 /* variables that take int values */
883 else {
884 int *ip = (int *)p;
885 *ip = (int)strtoul(val, &endp, 10);
887 if(var_is("show_status", name)) {
888 cmd_set_status();
890 else if(var_is("always_insert_mode", name)) {
891 uzbl.behave.insert_mode =
892 uzbl.behave.always_insert_mode ? TRUE : FALSE;
893 update_title();
895 else if (var_is("max_conns", name)) {
896 g_object_set(G_OBJECT(uzbl.net.soup_session),
897 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
899 else if (var_is("max_conns_host", name)) {
900 g_object_set(G_OBJECT(uzbl.net.soup_session),
901 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
903 else if (var_is("http_debug", name)) {
904 //soup_session_remove_feature
905 // (uzbl.net.soup_session, uzbl.net.soup_logger);
906 soup_session_remove_feature
907 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
908 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
909 /*g_free(uzbl.net.soup_logger);*/
911 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
912 soup_session_add_feature(uzbl.net.soup_session,
913 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
915 else if (var_is("status_top", name)) {
916 move_statusbar();
920 return TRUE;
923 static void
924 runcmd(WebKitWebView* page, const char *param) {
925 (void) page;
926 parse_cmd_line(param);
929 static void
930 parse_cmd_line(const char *ctl_line) {
931 gchar **tokens;
933 /* SET command */
934 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
935 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
936 if(tokens[0][0] == 0) {
937 set_var_value(tokens[1], tokens[2]);
938 g_strfreev(tokens);
940 else
941 printf("Error in command: %s\n", tokens[0]);
943 /* GET command */
944 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
945 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
946 if(tokens[0][0] == 0) {
947 get_var_value(tokens[1]);
948 g_strfreev(tokens);
950 else
951 printf("Error in command: %s\n", tokens[0]);
953 /* BIND command */
954 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
955 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
956 if(tokens[0][0] == 0) {
957 add_binding(tokens[1], tokens[2]);
958 g_strfreev(tokens);
960 else
961 printf("Error in command: %s\n", tokens[0]);
963 /* ACT command */
964 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
965 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
966 if(tokens[0][0] == 0) {
967 parse_command(tokens[1], tokens[2]);
968 g_strfreev(tokens);
970 else
971 printf("Error in command: %s\n", tokens[0]);
973 /* KEYCMD command */
974 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
975 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
976 if(tokens[0][0] == 0) {
977 /* should incremental commands want each individual "keystroke"
978 sent in a loop or the whole string in one go like now? */
979 g_string_assign(uzbl.state.keycmd, tokens[1]);
980 run_keycmd(FALSE);
981 update_title();
982 g_strfreev(tokens);
985 /* Comments */
986 else if( (ctl_line[0] == '#')
987 || (ctl_line[0] == ' ')
988 || (ctl_line[0] == '\n'))
989 ; /* ignore these lines */
990 else
991 printf("Command not understood (%s)\n", ctl_line);
993 return;
996 static gchar*
997 build_stream_name(int type, const gchar* dir) {
998 char *xwin_str;
999 State *s = &uzbl.state;
1000 gchar *str;
1002 xwin_str = itos((int)uzbl.xwin);
1003 if (type == FIFO) {
1004 str = g_strdup_printf
1005 ("%s/uzbl_fifo_%s", dir,
1006 s->instance_name ? s->instance_name : xwin_str);
1007 } else if (type == SOCKET) {
1008 str = g_strdup_printf
1009 ("%s/uzbl_socket_%s", dir,
1010 s->instance_name ? s->instance_name : xwin_str );
1012 g_free(xwin_str);
1013 return str;
1016 static gboolean
1017 control_fifo(GIOChannel *gio, GIOCondition condition) {
1018 if (uzbl.state.verbose)
1019 printf("triggered\n");
1020 gchar *ctl_line;
1021 GIOStatus ret;
1022 GError *err = NULL;
1024 if (condition & G_IO_HUP)
1025 g_error ("Fifo: Read end of pipe died!\n");
1027 if(!gio)
1028 g_error ("Fifo: GIOChannel broke\n");
1030 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1031 if (ret == G_IO_STATUS_ERROR)
1032 g_error ("Fifo: Error reading: %s\n", err->message);
1034 parse_cmd_line(ctl_line);
1035 g_free(ctl_line);
1037 return TRUE;
1040 static gchar*
1041 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1042 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1043 if (unlink(uzbl.comm.fifo_path) == -1)
1044 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1045 g_free(uzbl.comm.fifo_path);
1046 uzbl.comm.fifo_path = NULL;
1049 if (*dir == ' ') { /* space unsets the variable */
1050 g_free(dir);
1051 return NULL;
1054 GIOChannel *chan = NULL;
1055 GError *error = NULL;
1056 gchar *path = build_stream_name(FIFO, dir);
1058 if (!file_exists(path)) {
1059 if (mkfifo (path, 0666) == 0) {
1060 // 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.
1061 chan = g_io_channel_new_file(path, "r+", &error);
1062 if (chan) {
1063 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1064 if (uzbl.state.verbose)
1065 printf ("init_fifo: created successfully as %s\n", path);
1066 uzbl.comm.fifo_path = path;
1067 return dir;
1068 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1069 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1070 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1071 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1073 /* if we got this far, there was an error; cleanup */
1074 g_free(path);
1075 g_free(dir);
1076 return NULL;
1079 static gboolean
1080 control_stdin(GIOChannel *gio, GIOCondition condition) {
1081 gchar *ctl_line = NULL;
1082 gsize ctl_line_len = 0;
1083 GIOStatus ret;
1084 GError *err = NULL;
1086 if (condition & G_IO_HUP) {
1087 ret = g_io_channel_shutdown (gio, FALSE, &err);
1088 return FALSE;
1091 ret = g_io_channel_read_line(gio, &ctl_line, &ctl_line_len, NULL, &err);
1092 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1093 return FALSE;
1095 parse_cmd_line(ctl_line);
1096 g_free(ctl_line);
1098 return TRUE;
1101 static void
1102 create_stdin () {
1103 GIOChannel *chan = NULL;
1104 GError *error = NULL;
1106 chan = g_io_channel_unix_new(fileno(stdin));
1107 if (chan) {
1108 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1109 g_error ("Stdin: could not add watch\n");
1110 } else {
1111 printf ("Stdin: watch added successfully\n");
1113 } else {
1114 g_error ("Stdin: Error while opening: %s\n", error->message);
1118 static gboolean
1119 control_socket(GIOChannel *chan) {
1120 struct sockaddr_un remote;
1121 char buffer[512], *ctl_line;
1122 char temp[128];
1123 int sock, clientsock, n, done;
1124 unsigned int t;
1126 sock = g_io_channel_unix_get_fd(chan);
1128 memset (buffer, 0, sizeof (buffer));
1130 t = sizeof (remote);
1131 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1133 done = 0;
1134 do {
1135 memset (temp, 0, sizeof (temp));
1136 n = recv (clientsock, temp, 128, 0);
1137 if (n == 0) {
1138 buffer[strlen (buffer)] = '\0';
1139 done = 1;
1141 if (!done)
1142 strcat (buffer, temp);
1143 } while (!done);
1145 if (strcmp (buffer, "\n") < 0) {
1146 buffer[strlen (buffer) - 1] = '\0';
1147 } else {
1148 buffer[strlen (buffer)] = '\0';
1150 close (clientsock);
1151 ctl_line = g_strdup(buffer);
1152 parse_cmd_line (ctl_line);
1155 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1156 GError *error = NULL;
1157 gsize len;
1158 GIOStatus ret;
1159 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1160 if (ret == G_IO_STATUS_ERROR)
1161 g_error ("Error reading: %s\n", error->message);
1163 printf("Got line %s (%u bytes) \n",ctl_line, len);
1164 if(ctl_line) {
1165 parse_line(ctl_line);
1168 g_free(ctl_line);
1169 return TRUE;
1172 static gchar*
1173 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1174 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1175 if (unlink(uzbl.comm.socket_path) == -1)
1176 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1177 g_free(uzbl.comm.socket_path);
1178 uzbl.comm.socket_path = NULL;
1181 if (*dir == ' ') {
1182 g_free(dir);
1183 return NULL;
1186 GIOChannel *chan = NULL;
1187 int sock, len;
1188 struct sockaddr_un local;
1189 gchar *path = build_stream_name(SOCKET, dir);
1191 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1193 local.sun_family = AF_UNIX;
1194 strcpy (local.sun_path, path);
1195 unlink (local.sun_path);
1197 len = strlen (local.sun_path) + sizeof (local.sun_family);
1198 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1199 printf ("init_socket: opened in %s\n", path);
1200 listen (sock, 5);
1202 if( (chan = g_io_channel_unix_new(sock)) ) {
1203 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1204 uzbl.comm.socket_path = path;
1205 return dir;
1207 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1209 /* if we got this far, there was an error; cleanup */
1210 g_free(path);
1211 g_free(dir);
1212 return NULL;
1215 static void
1216 update_title (void) {
1217 GString* string_long = g_string_new ("");
1218 GString* string_short = g_string_new ("");
1219 char* iname = NULL;
1220 gchar *statln;
1221 int iname_len;
1222 State *s = &uzbl.state;
1223 Behaviour *b = &uzbl.behave;
1225 if(s->instance_name) {
1226 iname_len = strlen(s->instance_name)+4;
1227 iname = malloc(iname_len);
1228 snprintf(iname, iname_len, "<%s> ", s->instance_name);
1230 g_string_prepend(string_long, iname);
1231 g_string_prepend(string_short, iname);
1232 free(iname);
1235 g_string_append_printf(string_long, "%s ", s->keycmd->str);
1236 if (!b->always_insert_mode)
1237 g_string_append (string_long, (b->insert_mode ? "[I] " : "[C] "));
1238 if (uzbl.gui.main_title) {
1239 g_string_append (string_long, uzbl.gui.main_title);
1240 g_string_append (string_short, uzbl.gui.main_title);
1242 g_string_append (string_long, " - Uzbl browser");
1243 g_string_append (string_short, " - Uzbl browser");
1244 if (s->selected_url[0]!=0) {
1245 g_string_append_printf (string_long, " -> (%s)", s->selected_url);
1248 gchar* title_long = g_string_free (string_long, FALSE);
1249 gchar* title_short = g_string_free (string_short, FALSE);
1251 if (b->show_status) {
1252 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), title_short);
1253 // TODO: we should probably not do this every time we want to update the title..?
1254 statln = expand_template(uzbl.behave.status_format);
1255 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), statln);
1256 if (b->status_background) {
1257 GdkColor color;
1258 gdk_color_parse (b->status_background, &color);
1259 //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)
1260 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1262 g_free(statln);
1263 } else {
1264 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), title_long);
1267 g_free (title_long);
1268 g_free (title_short);
1271 static gboolean
1272 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1274 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1276 (void) page;
1278 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1279 || 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)
1280 return FALSE;
1282 /* turn off insert mode (if always_insert_mode is not used) */
1283 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1284 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1285 update_title();
1286 return TRUE;
1289 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1290 return FALSE;
1292 if (event->keyval == GDK_Escape) {
1293 g_string_truncate(uzbl.state.keycmd, 0);
1294 update_title();
1295 return TRUE;
1298 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1299 if (event->keyval == GDK_Insert) {
1300 gchar * str;
1301 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1302 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1303 } else {
1304 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1306 if (str) {
1307 g_string_append (uzbl.state.keycmd, str);
1308 update_title ();
1309 free (str);
1311 return TRUE;
1314 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1315 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1316 update_title();
1319 gboolean key_ret = FALSE;
1320 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1321 key_ret = TRUE;
1322 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1324 run_keycmd(key_ret);
1325 update_title();
1326 if (key_ret) return (!uzbl.behave.insert_mode);
1327 return TRUE;
1330 static void
1331 run_keycmd(const gboolean key_ret) {
1332 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1333 Action *action;
1334 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1335 g_string_truncate(uzbl.state.keycmd, 0);
1336 parse_command(action->name, action->param);
1337 return;
1340 /* try if it's an incremental keycmd or one that takes args, and run it */
1341 GString* short_keys = g_string_new ("");
1342 GString* short_keys_inc = g_string_new ("");
1343 unsigned int i;
1344 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1345 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1346 g_string_assign(short_keys_inc, short_keys->str);
1347 g_string_append_c(short_keys, '_');
1348 g_string_append_c(short_keys_inc, '*');
1350 gboolean exec_now = FALSE;
1351 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1352 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1353 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1354 if (key_ret) { /* just quit the incremental command on return */
1355 g_string_truncate(uzbl.state.keycmd, 0);
1356 break;
1357 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1360 if (exec_now) {
1361 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1362 GString* actionname = g_string_new ("");
1363 GString* actionparam = g_string_new ("");
1364 g_string_erase (parampart, 0, i+1);
1365 if (action->name)
1366 g_string_printf (actionname, action->name, parampart->str);
1367 if (action->param)
1368 g_string_printf (actionparam, action->param, parampart->str);
1369 parse_command(actionname->str, actionparam->str);
1370 g_string_free (actionname, TRUE);
1371 g_string_free (actionparam, TRUE);
1372 g_string_free (parampart, TRUE);
1373 if (key_ret)
1374 g_string_truncate(uzbl.state.keycmd, 0);
1375 break;
1378 g_string_truncate(short_keys, short_keys->len - 1);
1380 g_string_free (short_keys, TRUE);
1381 g_string_free (short_keys_inc, TRUE);
1384 static GtkWidget*
1385 create_browser () {
1386 GUI *g = &uzbl.gui;
1388 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1389 //main_window_ref = g_object_ref(scrolled_window);
1390 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
1392 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1393 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1395 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1396 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1397 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1398 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1399 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1400 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1401 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1402 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1403 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1404 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1406 return scrolled_window;
1409 static GtkWidget*
1410 create_mainbar () {
1411 GUI *g = &uzbl.gui;
1413 g->mainbar = gtk_hbox_new (FALSE, 0);
1415 /* keep a reference to the bar so we can re-pack it at runtime*/
1416 //sbar_ref = g_object_ref(g->mainbar);
1418 g->mainbar_label = gtk_label_new ("");
1419 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1420 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1421 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1422 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1423 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1424 return g->mainbar;
1427 static
1428 GtkWidget* create_window () {
1429 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1430 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1431 gtk_widget_set_name (window, "Uzbl browser");
1432 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1434 return window;
1437 static void
1438 add_binding (const gchar *key, const gchar *act) {
1439 char **parts = g_strsplit(act, " ", 2);
1440 Action *action;
1442 if (!parts)
1443 return;
1445 //Debug:
1446 if (uzbl.state.verbose)
1447 printf ("Binding %-10s : %s\n", key, act);
1448 action = new_action(parts[0], parts[1]);
1450 if(g_hash_table_lookup(uzbl.bindings, key))
1451 g_hash_table_remove(uzbl.bindings, key);
1452 g_hash_table_insert(uzbl.bindings, g_strdup(key), action);
1454 g_strfreev(parts);
1457 static gchar*
1458 get_xdg_var (XDG_Var xdg) {
1459 const gchar* actual_value = getenv (xdg.environmental);
1460 const gchar* home = getenv ("HOME");
1462 gchar* return_value = str_replace ("~", home, g_strdup (actual_value));
1464 if (! actual_value || strcmp (actual_value, "") == 0) {
1465 if (xdg.default_value) {
1466 return_value = str_replace ("~", home, g_strdup (xdg.default_value));
1467 } else {
1468 return_value = NULL;
1472 return return_value;
1475 static gchar*
1476 find_xdg_file (int xdg_type, char* filename) {
1477 /* xdg_type = 0 => config
1478 xdg_type = 1 => data
1479 xdg_type = 2 => cache*/
1481 gchar* xdg_config_home = get_xdg_var (XDG[0]);
1482 gchar* xdg_data_home = get_xdg_var (XDG[1]);
1483 gchar* xdg_cache_home = get_xdg_var (XDG[2]);
1484 gchar* xdg_config_dirs = get_xdg_var (XDG[3]);
1485 gchar* xdg_data_dirs = get_xdg_var (XDG[4]);
1486 gchar* temporary_file = (char *)malloc (1024);
1487 gchar* temporary_string = NULL;
1488 char* saveptr;
1490 if (xdg_type == 0)
1491 strcpy (temporary_file, xdg_config_home);
1493 if (xdg_type == 1)
1494 strcpy (temporary_file, xdg_data_home);
1496 if (xdg_type == 2)
1497 strcpy (temporary_file, xdg_cache_home);
1499 strcat (temporary_file, filename);
1501 if (! file_exists (temporary_file) && xdg_type != 2) {
1502 if (xdg_type == 0)
1503 temporary_string = (char *) strtok_r (xdg_config_dirs, ":", &saveptr);
1505 if (xdg_type == 1)
1506 temporary_string = (char *) strtok_r (xdg_data_dirs, ":", &saveptr);
1508 while (temporary_string && ! file_exists (temporary_file)) {
1509 strcpy (temporary_file, temporary_string);
1510 strcat (temporary_file, filename);
1511 temporary_string = (char * ) strtok_r (NULL, ":", &saveptr);
1515 if (file_exists (temporary_file)) {
1516 return temporary_file;
1517 } else {
1518 return NULL;
1522 static void
1523 settings_init () {
1524 char *saveptr;
1525 State *s = &uzbl.state;
1526 Network *n = &uzbl.net;
1528 uzbl.behave.reset_command_mode = 1;
1530 if (!s->config_file) {
1531 s->config_file = g_strdup (find_xdg_file (0, "/uzbl/config"));
1534 if (s->config_file) {
1535 GIOChannel *chan = NULL;
1536 GError *error = NULL;
1537 gchar *readbuf = NULL;
1538 gsize len;
1540 chan = g_io_channel_new_file(s->config_file, "r", &error);
1542 if (chan) {
1543 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1544 == G_IO_STATUS_NORMAL) {
1545 parse_cmd_line(readbuf);
1546 g_free (readbuf);
1549 g_io_channel_unref (chan);
1550 printf ("Config %s loaded\n", s->config_file);
1551 } else {
1552 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1554 } else {
1555 printf ("No configuration file loaded.\n");
1557 if (!uzbl.behave.status_format)
1558 uzbl.behave.status_format = g_strdup(STATUS_DEFAULT);
1560 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1563 static gchar*
1564 set_useragent(gchar *val) {
1565 if (*val == ' ') {
1566 g_free(val);
1567 return NULL;
1569 gchar *ua = expand_template(val);
1570 if (ua)
1571 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1572 return ua;
1575 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1576 (void) session;
1577 (void) user_data;
1578 if (!uzbl.behave.cookie_handler) return;
1580 gchar * stdout = NULL;
1581 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1582 GString* args = g_string_new ("");
1583 SoupURI * soup_uri = soup_message_get_uri(msg);
1584 g_string_printf (args, "GET %s %s", soup_uri->host, soup_uri->path);
1585 run_command(uzbl.behave.cookie_handler, args->str, TRUE, &stdout);
1586 if(stdout) {
1587 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1589 g_string_free(args, TRUE);
1592 static void
1593 save_cookies (SoupMessage *msg, gpointer user_data){
1594 (void) user_data;
1595 GSList *ck;
1596 char *cookie;
1597 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1598 cookie = soup_cookie_to_set_cookie_header(ck->data);
1599 GString* args = g_string_new ("");
1600 SoupURI * soup_uri = soup_message_get_uri(msg);
1601 g_string_printf (args, "PUT %s %s \"%s\"", soup_uri->host, soup_uri->path, cookie);
1602 run_command(uzbl.behave.cookie_handler, args->str, FALSE, NULL);
1603 g_string_free(args, TRUE);
1604 free(cookie);
1606 g_slist_free(ck);
1611 main (int argc, char* argv[]) {
1612 gtk_init (&argc, &argv);
1613 if (!g_thread_supported ())
1614 g_thread_init (NULL);
1616 strcpy(uzbl.state.executable_path,argv[0]);
1618 GError *error = NULL;
1619 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1620 g_option_context_add_main_entries (context, entries, NULL);
1621 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1622 g_option_context_parse (context, &argc, &argv, &error);
1623 /* initialize hash table */
1624 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1626 uzbl.net.soup_session = webkit_get_default_session();
1627 uzbl.state.keycmd = g_string_new("");
1629 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1630 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1631 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1632 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1634 if(uname(&uzbl.state.unameinfo) == -1)
1635 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1637 setup_regex();
1638 setup_scanner();
1639 commands_hash ();
1640 make_var_to_name_hash();
1643 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1645 uzbl.gui.scrolled_win = create_browser();
1646 create_mainbar();
1648 /* initial packing */
1649 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1650 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1652 uzbl.gui.main_window = create_window ();
1653 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1655 load_uri (uzbl.gui.web_view, uzbl.state.uri); //TODO: is this needed?
1657 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1658 gtk_widget_show_all (uzbl.gui.main_window);
1659 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1661 if (uzbl.state.verbose) {
1662 printf("Uzbl start location: %s\n", argv[0]);
1663 printf("window_id %i\n",(int) uzbl.xwin);
1664 printf("pid %i\n", getpid ());
1665 printf("name: %s\n", uzbl.state.instance_name);
1668 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1669 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1670 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1671 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1672 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1674 settings_init ();
1676 if (!uzbl.behave.show_status)
1677 gtk_widget_hide(uzbl.gui.mainbar);
1678 else
1679 update_title();
1681 create_stdin();
1683 gtk_main ();
1684 clean_up();
1686 return EXIT_SUCCESS;
1689 /* vi: set et ts=4: */