layout fixes
[uzbl-00z.git] / uzbl.c
blob5dd1da30de0c2ed9ea0ad76db58585c25e5ecd7b
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 { "name", 'n', 0, G_OPTION_ARG_STRING, &uzbl.state.instance_name, "Name of the current instance (defaults to Xorg window id)", "NAME" },
128 { "config", 'c', 0, G_OPTION_ARG_STRING, &uzbl.state.config_file, "Config file", "FILE" },
129 { NULL, 0, 0, 0, NULL, NULL, NULL }
132 typedef void (*Command)(WebKitWebView*, const char *);
134 /* XDG stuff */
135 static char *XDG_CONFIG_HOME_default[256];
136 static char *XDG_CONFIG_DIRS_default = "/etc/xdg";
139 /* --- UTILITY FUNCTIONS --- */
141 char *
142 itos(int val) {
143 char tmp[20];
145 snprintf(tmp, sizeof(tmp), "%i", val);
146 return g_strdup(tmp);
149 static sigfunc*
150 setup_signal(int signr, sigfunc *shandler) {
151 struct sigaction nh, oh;
153 nh.sa_handler = shandler;
154 sigemptyset(&nh.sa_mask);
155 nh.sa_flags = 0;
157 if(sigaction(signr, &nh, &oh) < 0)
158 return SIG_ERR;
160 return NULL;
163 static void
164 clean_up(void) {
165 if (uzbl.behave.fifo_dir)
166 unlink (uzbl.comm.fifo_path);
167 if (uzbl.behave.socket_dir)
168 unlink (uzbl.comm.socket_path);
170 g_string_free(uzbl.state.keycmd, TRUE);
171 g_hash_table_destroy(uzbl.bindings);
172 g_hash_table_destroy(uzbl.behave.commands);
176 /* --- SIGNAL HANDLER --- */
178 static void
179 catch_sigterm(int s) {
180 (void) s;
181 clean_up();
184 static void
185 catch_sigint(int s) {
186 (void) s;
187 clean_up();
188 exit(EXIT_SUCCESS);
191 /* --- CALLBACKS --- */
193 static gboolean
194 new_window_cb (WebKitWebView *web_view, WebKitWebFrame *frame, WebKitNetworkRequest *request, WebKitWebNavigationAction *navigation_action, WebKitWebPolicyDecision *policy_decision, gpointer user_data) {
195 (void) web_view;
196 (void) frame;
197 (void) navigation_action;
198 (void) policy_decision;
199 (void) user_data;
200 const gchar* uri = webkit_network_request_get_uri (request);
201 printf("New window requested -> %s \n", uri);
202 new_window_load_uri(uri);
203 return (FALSE);
206 WebKitWebView*
207 create_web_view_cb (WebKitWebView *web_view, WebKitWebFrame *frame, gpointer user_data) {
208 (void) web_view;
209 (void) frame;
210 (void) user_data;
211 if (uzbl.state.selected_url[0]!=0) {
212 printf("\nNew web view -> %s\n",uzbl.state.selected_url);
213 new_window_load_uri(uzbl.state.selected_url);
214 } else {
215 printf("New web view -> %s\n","Nothing to open, exiting");
217 return (NULL);
220 static gboolean
221 download_cb (WebKitWebView *web_view, GObject *download, gpointer user_data) {
222 (void) web_view;
223 (void) user_data;
224 if (uzbl.behave.download_handler) {
225 const gchar* uri = webkit_download_get_uri ((WebKitDownload*)download);
226 printf("Download -> %s\n",uri);
227 run_command(uzbl.behave.download_handler, uri, FALSE, NULL);
229 return (FALSE);
232 /* scroll a bar in a given direction */
233 static void
234 scroll (GtkAdjustment* bar, const char *param) {
235 gdouble amount;
236 gchar *end;
238 amount = g_ascii_strtod(param, &end);
240 if (*end)
241 fprintf(stderr, "found something after double: %s\n", end);
243 gtk_adjustment_set_value (bar, gtk_adjustment_get_value(bar)+amount);
246 static void scroll_begin(WebKitWebView* page, const char *param) {
247 (void) page; (void) param;
248 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_lower(uzbl.gui.bar_v));
251 static void scroll_end(WebKitWebView* page, const char *param) {
252 (void) page; (void) param;
253 gtk_adjustment_set_value (uzbl.gui.bar_v, gtk_adjustment_get_upper(uzbl.gui.bar_v) -
254 gtk_adjustment_get_page_size(uzbl.gui.bar_v));
257 static void scroll_vert(WebKitWebView* page, const char *param) {
258 (void) page;
259 scroll(uzbl.gui.bar_v, param);
262 static void scroll_horz(WebKitWebView* page, const char *param) {
263 (void) page;
264 scroll(uzbl.gui.bar_h, param);
267 static void
268 cmd_set_status() {
269 if (!uzbl.behave.show_status) {
270 gtk_widget_hide(uzbl.gui.mainbar);
271 } else {
272 gtk_widget_show(uzbl.gui.mainbar);
274 update_title();
277 static void
278 toggle_status_cb (WebKitWebView* page, const char *param) {
279 (void)page;
280 (void)param;
282 if (uzbl.behave.show_status) {
283 gtk_widget_hide(uzbl.gui.mainbar);
284 } else {
285 gtk_widget_show(uzbl.gui.mainbar);
287 uzbl.behave.show_status = !uzbl.behave.show_status;
288 update_title();
291 static void
292 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
293 (void) page;
294 (void) title;
295 (void) data;
296 //ADD HOVER URL TO WINDOW TITLE
297 uzbl.state.selected_url[0] = '\0';
298 if (link) {
299 strcpy (uzbl.state.selected_url, link);
301 update_title();
304 static void
305 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
306 (void) web_view;
307 (void) web_frame;
308 (void) data;
309 if (uzbl.gui.main_title)
310 g_free (uzbl.gui.main_title);
311 uzbl.gui.main_title = g_strdup (title);
312 update_title();
315 static void
316 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
317 (void) page;
318 (void) data;
319 uzbl.gui.sbar.load_progress = progress;
320 update_title();
323 static void
324 load_finish_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
325 (void) page;
326 (void) frame;
327 (void) data;
328 if (uzbl.behave.load_finish_handler) {
329 run_command(uzbl.behave.load_finish_handler, NULL, FALSE, NULL);
333 static void
334 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
335 (void) page;
336 (void) data;
337 free (uzbl.state.uri);
338 GString* newuri = g_string_new (webkit_web_frame_get_uri (frame));
339 uzbl.state.uri = g_string_free (newuri, FALSE);
340 if (uzbl.behave.reset_command_mode && uzbl.behave.insert_mode) {
341 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
342 update_title();
344 g_string_truncate(uzbl.state.keycmd, 0); // don't need old commands to remain on new page?
347 static void
348 destroy_cb (GtkWidget* widget, gpointer data) {
349 (void) widget;
350 (void) data;
351 gtk_main_quit ();
354 static void
355 log_history_cb () {
356 if (uzbl.behave.history_handler) {
357 time_t rawtime;
358 struct tm * timeinfo;
359 char date [80];
360 time ( &rawtime );
361 timeinfo = localtime ( &rawtime );
362 strftime (date, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
363 GString* args = g_string_new ("");
364 g_string_printf (args, "'%s'", date);
365 run_command(uzbl.behave.history_handler, args->str, FALSE, NULL);
366 g_string_free (args, TRUE);
371 /* VIEW funcs (little webkit wrappers) */
372 #define VIEWFUNC(name) static void view_##name(WebKitWebView *page, const char *param){(void)param; webkit_web_view_##name(page);}
373 VIEWFUNC(reload)
374 VIEWFUNC(reload_bypass_cache)
375 VIEWFUNC(stop_loading)
376 VIEWFUNC(zoom_in)
377 VIEWFUNC(zoom_out)
378 VIEWFUNC(go_back)
379 VIEWFUNC(go_forward)
380 #undef VIEWFUNC
382 /* -- command to callback/function map for things we cannot attach to any signals */
383 // TODO: reload
385 static struct {char *name; Command command;} cmdlist[] =
387 { "back", view_go_back },
388 { "forward", view_go_forward },
389 { "scroll_vert", scroll_vert },
390 { "scroll_horz", scroll_horz },
391 { "scroll_begin", scroll_begin },
392 { "scroll_end", scroll_end },
393 { "reload", view_reload, },
394 { "reload_ign_cache", view_reload_bypass_cache},
395 { "stop", view_stop_loading, },
396 { "zoom_in", view_zoom_in, }, //Can crash (when max zoom reached?).
397 { "zoom_out", view_zoom_out, },
398 { "uri", load_uri },
399 { "script", run_js },
400 { "toggle_status", toggle_status_cb },
401 { "spawn", spawn },
402 { "exit", close_uzbl },
403 { "search", search_text },
404 { "insert_mode", set_insert_mode },
405 { "runcmd", runcmd }
408 static void
409 commands_hash(void)
411 unsigned int i;
412 uzbl.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
414 for (i = 0; i < LENGTH(cmdlist); i++)
415 g_hash_table_insert(uzbl.behave.commands, cmdlist[i].name, cmdlist[i].command);
418 /* -- CORE FUNCTIONS -- */
420 void
421 free_action(gpointer act) {
422 Action *action = (Action*)act;
423 g_free(action->name);
424 if (action->param)
425 g_free(action->param);
426 g_free(action);
429 Action*
430 new_action(const gchar *name, const gchar *param) {
431 Action *action = g_new(Action, 1);
433 action->name = g_strdup(name);
434 if (param)
435 action->param = g_strdup(param);
436 else
437 action->param = NULL;
439 return action;
442 static bool
443 file_exists (const char * filename) {
444 return (access(filename, F_OK) == 0);
447 void
448 set_insert_mode(WebKitWebView *page, const gchar *param) {
449 (void)page;
450 (void)param;
452 uzbl.behave.insert_mode = TRUE;
453 update_title();
456 static void
457 load_uri (WebKitWebView * web_view, const gchar *param) {
458 if (param) {
459 GString* newuri = g_string_new (param);
460 if (g_strrstr (param, "://") == NULL)
461 g_string_prepend (newuri, "http://");
462 /* if we do handle cookies, ask our handler for them */
463 webkit_web_view_load_uri (web_view, newuri->str);
464 g_string_free (newuri, TRUE);
468 static void
469 run_js (WebKitWebView * web_view, const gchar *param) {
470 if (param)
471 webkit_web_view_execute_script (web_view, param);
474 static void
475 search_text (WebKitWebView *page, const char *param) {
476 if ((param) && (param[0] != '\0')) {
477 strcpy(uzbl.state.searchtx, param);
479 if (uzbl.state.searchtx[0] != '\0') {
480 printf ("Searching: %s\n", uzbl.state.searchtx);
481 webkit_web_view_unmark_text_matches (page);
482 webkit_web_view_mark_text_matches (page, uzbl.state.searchtx, FALSE, 0);
483 webkit_web_view_set_highlight_text_matches (page, TRUE);
484 webkit_web_view_search_text (page, uzbl.state.searchtx, FALSE, TRUE, TRUE);
488 static void
489 new_window_load_uri (const gchar * uri) {
490 GString* to_execute = g_string_new ("");
491 g_string_append_printf (to_execute, "%s --uri '%s'", uzbl.state.executable_path, uri);
492 int i;
493 for (i = 0; entries[i].long_name != NULL; i++) {
494 if ((entries[i].arg == G_OPTION_ARG_STRING) && (strcmp(entries[i].long_name,"uri")!=0)) {
495 gchar** str = (gchar**)entries[i].arg_data;
496 if (*str!=NULL) {
497 g_string_append_printf (to_execute, " --%s '%s'", entries[i].long_name, *str);
501 printf("\n%s\n", to_execute->str);
502 g_spawn_command_line_async (to_execute->str, NULL);
503 g_string_free (to_execute, TRUE);
506 static void
507 close_uzbl (WebKitWebView *page, const char *param) {
508 (void)page;
509 (void)param;
510 gtk_main_quit ();
513 /* --Statusbar functions-- */
514 static char*
515 build_progressbar_ascii(int percent) {
516 int width=10;
517 int i;
518 double l;
519 GString *bar = g_string_new("");
521 l = (double)percent*((double)width/100.);
522 l = (int)(l+.5)>=(int)l ? l+.5 : l;
524 for(i=0; i<(int)l; i++)
525 g_string_append(bar, "=");
527 for(; i<width; i++)
528 g_string_append(bar, "·");
530 return g_string_free(bar, FALSE);
533 static void
534 setup_scanner() {
535 const GScannerConfig scan_config = {
537 "\t\r\n"
538 ) /* cset_skip_characters */,
540 G_CSET_a_2_z
541 "_#"
542 G_CSET_A_2_Z
543 ) /* cset_identifier_first */,
545 G_CSET_a_2_z
546 "_0123456789"
547 G_CSET_A_2_Z
548 G_CSET_LATINS
549 G_CSET_LATINC
550 ) /* cset_identifier_nth */,
551 ( "" ) /* cpair_comment_single */,
553 TRUE /* case_sensitive */,
555 FALSE /* skip_comment_multi */,
556 FALSE /* skip_comment_single */,
557 FALSE /* scan_comment_multi */,
558 TRUE /* scan_identifier */,
559 TRUE /* scan_identifier_1char */,
560 FALSE /* scan_identifier_NULL */,
561 TRUE /* scan_symbols */,
562 FALSE /* scan_binary */,
563 FALSE /* scan_octal */,
564 FALSE /* scan_float */,
565 FALSE /* scan_hex */,
566 FALSE /* scan_hex_dollar */,
567 FALSE /* scan_string_sq */,
568 FALSE /* scan_string_dq */,
569 TRUE /* numbers_2_int */,
570 FALSE /* int_2_float */,
571 FALSE /* identifier_2_string */,
572 FALSE /* char_2_token */,
573 FALSE /* symbol_2_token */,
574 TRUE /* scope_0_fallback */,
575 FALSE,
576 TRUE
579 uzbl.scan = g_scanner_new(&scan_config);
580 while(symp->symbol_name) {
581 g_scanner_scope_add_symbol(uzbl.scan, 0,
582 symp->symbol_name,
583 GINT_TO_POINTER(symp->symbol_token));
584 symp++;
588 static gchar *
589 expand_template(const char *template) {
590 if(!template) return NULL;
592 GTokenType token = G_TOKEN_NONE;
593 GString *ret = g_string_new("");
594 char *buf=NULL;
595 int sym;
597 g_scanner_input_text(uzbl.scan, template, strlen(template));
598 while(!g_scanner_eof(uzbl.scan) && token != G_TOKEN_LAST) {
599 token = g_scanner_get_next_token(uzbl.scan);
601 if(token == G_TOKEN_SYMBOL) {
602 sym = (int)g_scanner_cur_value(uzbl.scan).v_symbol;
603 switch(sym) {
604 case SYM_URI:
605 g_string_append(ret,
606 uzbl.state.uri?
607 g_markup_printf_escaped("%s", uzbl.state.uri):"");
608 break;
609 case SYM_LOADPRGS:
610 buf = itos(uzbl.gui.sbar.load_progress);
611 g_string_append(ret, buf);
612 free(buf);
613 break;
614 case SYM_LOADPRGSBAR:
615 buf = build_progressbar_ascii(uzbl.gui.sbar.load_progress);
616 g_string_append(ret, buf);
617 g_free(buf);
618 break;
619 case SYM_TITLE:
620 g_string_append(ret,
621 uzbl.gui.main_title?
622 g_markup_printf_escaped("%s", uzbl.gui.main_title):"");
623 break;
624 case SYM_NAME:
625 buf = itos(uzbl.xwin);
626 g_string_append(ret,
627 uzbl.state.instance_name?uzbl.state.instance_name:buf);
628 free(buf);
629 break;
630 case SYM_KEYCMD:
631 g_string_append(ret,
632 uzbl.state.keycmd->str ?
633 g_markup_printf_escaped("%s", uzbl.state.keycmd->str):"");
634 break;
635 case SYM_MODE:
636 g_string_append(ret,
637 uzbl.behave.insert_mode?"[I]":"[C]");
638 break;
639 case SYM_MSG:
640 g_string_append(ret,
641 uzbl.gui.sbar.msg?uzbl.gui.sbar.msg:"");
642 break;
643 /* useragent syms */
644 case SYM_WK_MAJ:
645 buf = itos(WEBKIT_MAJOR_VERSION);
646 g_string_append(ret, buf);
647 free(buf);
648 break;
649 case SYM_WK_MIN:
650 buf = itos(WEBKIT_MINOR_VERSION);
651 g_string_append(ret, buf);
652 free(buf);
653 break;
654 case SYM_WK_MIC:
655 buf = itos(WEBKIT_MICRO_VERSION);
656 g_string_append(ret, buf);
657 free(buf);
658 break;
659 case SYM_SYSNAME:
660 g_string_append(ret, uzbl.state.unameinfo.sysname);
661 break;
662 case SYM_NODENAME:
663 g_string_append(ret, uzbl.state.unameinfo.nodename);
664 break;
665 case SYM_KERNREL:
666 g_string_append(ret, uzbl.state.unameinfo.release);
667 break;
668 case SYM_KERNVER:
669 g_string_append(ret, uzbl.state.unameinfo.version);
670 break;
671 case SYM_ARCHSYS:
672 g_string_append(ret, uzbl.state.unameinfo.machine);
673 break;
674 case SYM_ARCHUZBL:
675 g_string_append(ret, ARCH);
676 break;
677 #ifdef _GNU_SOURCE
678 case SYM_DOMAINNAME:
679 g_string_append(ret, uzbl.state.unameinfo.domainname);
680 break;
681 #endif
682 case SYM_COMMIT:
683 g_string_append(ret, COMMIT);
684 break;
685 default:
686 break;
689 else if(token == G_TOKEN_INT) {
690 buf = itos(g_scanner_cur_value(uzbl.scan).v_int);
691 g_string_append(ret, buf);
692 free(buf);
694 else if(token == G_TOKEN_IDENTIFIER) {
695 g_string_append(ret, (gchar *)g_scanner_cur_value(uzbl.scan).v_identifier);
697 else if(token == G_TOKEN_CHAR) {
698 g_string_append_c(ret, (gchar)g_scanner_cur_value(uzbl.scan).v_char);
702 return g_string_free(ret, FALSE);
704 /* --End Statusbar functions-- */
707 // make sure to put '' around args, so that if there is whitespace we can still keep arguments together.
708 static gboolean
709 run_command (const char *command, const char *args, const gboolean sync, char **stdout) {
710 //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> <uzbl socket file> [args]
711 GString* to_execute = g_string_new ("");
712 gboolean result;
713 g_string_printf (to_execute, "%s '%s' '%i' '%i' '%s' '%s'",
714 command, (uzbl.state.config_file ? uzbl.state.config_file : "(null)"),
715 (int) getpid(), (int) uzbl.xwin, uzbl.comm.fifo_path,
716 uzbl.comm.socket_path);
717 g_string_append_printf (to_execute, " '%s' '%s'",
718 uzbl.state.uri, uzbl.gui.main_title);
719 if(args) g_string_append_printf (to_execute, " %s", args);
721 if (sync) {
722 result = g_spawn_command_line_sync (to_execute->str, stdout, NULL, NULL, NULL);
723 } else result = g_spawn_command_line_async (to_execute->str, NULL);
724 printf("Called %s. Result: %s\n", to_execute->str, (result ? "TRUE" : "FALSE" ));
725 g_string_free (to_execute, TRUE);
726 return result;
729 static void
730 spawn(WebKitWebView *web_view, const char *param) {
731 (void)web_view;
732 run_command(param, NULL, FALSE, NULL);
735 static void
736 parse_command(const char *cmd, const char *param) {
737 Command c;
739 if ((c = g_hash_table_lookup(uzbl.behave.commands, cmd)))
740 c(uzbl.gui.web_view, param);
741 else
742 fprintf (stderr, "command \"%s\" not understood. ignoring.\n", cmd);
745 /* command parser */
746 static void
747 setup_regex() {
748 GError *err=NULL;
750 uzbl.comm.get_regex = g_regex_new("^[Gg][a-zA-Z]*\\s+([^ \\n]+)$",
751 G_REGEX_OPTIMIZE, 0, &err);
752 uzbl.comm.set_regex = g_regex_new("^[Ss][a-zA-Z]*\\s+([^ ]+)\\s*=\\s*([^\\n].*)$",
753 G_REGEX_OPTIMIZE, 0, &err);
754 uzbl.comm.bind_regex = g_regex_new("^[Bb][a-zA-Z]*\\s+?(.*[^ ])\\s*?=\\s*([a-z][^\\n].+)$",
755 G_REGEX_UNGREEDY|G_REGEX_OPTIMIZE, 0, &err);
756 uzbl.comm.act_regex = g_regex_new("^[Aa][a-zA-Z]*\\s+([^ \\n]+)\\s*([^\\n]*)?$",
757 G_REGEX_OPTIMIZE, 0, &err);
758 uzbl.comm.keycmd_regex = g_regex_new("^[Kk][a-zA-Z]*\\s+([^\\n]+)$",
759 G_REGEX_OPTIMIZE, 0, &err);
762 static gboolean
763 get_var_value(gchar *name) {
764 void **p = NULL;
766 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
767 if(var_is("status_format", name)
768 || var_is("useragent", name)) {
769 printf("VAR: %s VALUE: %s\n", name, (char *)*p);
770 } else printf("VAR: %s VALUE: %d\n", name, (int)*p);
772 return TRUE;
775 static void
776 set_proxy_url() {
777 SoupURI *suri;
779 if(*uzbl.net.proxy_url == ' '
780 || uzbl.net.proxy_url == NULL) {
781 soup_session_remove_feature_by_type(uzbl.net.soup_session,
782 (GType) SOUP_SESSION_PROXY_URI);
784 else {
785 suri = soup_uri_new(uzbl.net.proxy_url);
786 g_object_set(G_OBJECT(uzbl.net.soup_session),
787 SOUP_SESSION_PROXY_URI,
788 suri, NULL);
789 soup_uri_free(suri);
791 return;
795 static void
796 move_statusbar() {
797 gtk_widget_ref(uzbl.gui.scrolled_win);
798 gtk_widget_ref(uzbl.gui.mainbar);
799 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.scrolled_win);
800 gtk_container_remove(GTK_CONTAINER(uzbl.gui.vbox), uzbl.gui.mainbar);
802 if(uzbl.behave.status_top) {
803 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
804 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
806 else {
807 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
808 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
810 gtk_widget_unref(uzbl.gui.scrolled_win);
811 gtk_widget_unref(uzbl.gui.mainbar);
812 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
815 static gboolean
816 var_is(const char *x, const char *y) {
817 return (strcmp(x, y) == 0 ? TRUE : FALSE );
820 static gboolean
821 set_var_value(gchar *name, gchar *val) {
822 void **p = NULL;
823 char *endp = NULL;
825 if( (p = g_hash_table_lookup(uzbl.comm.proto_var, name)) ) {
826 if(var_is("status_message", name)
827 || var_is("status_background", name)
828 || var_is("status_format", name)
829 || var_is("load_finish_handler", name)
830 || var_is("history_handler", name)
831 || var_is("download_handler", name)
832 || var_is("cookie_handler", name)) {
833 if(*p)
834 free(*p);
835 *p = g_strdup(val);
836 update_title();
838 else if(var_is("uri", name)) {
839 if(*p) free(*p);
840 *p = g_strdup(val);
841 load_uri(uzbl.gui.web_view, (const gchar*)*p);
843 else if(var_is("proxy_url", name)) {
844 if(*p) free(*p);
845 *p = g_strdup(val);
846 set_proxy_url();
848 else if(var_is("fifo_dir", name)) {
849 if(*p) free(*p);
850 *p = init_fifo(g_strdup(val));
852 else if(var_is("socket_dir", name)) {
853 if(*p) free(*p);
854 *p = init_socket(g_strdup(val));
856 else if(var_is("modkey", name)) {
857 if(*p) free(*p);
858 int i;
859 *p = g_utf8_strup(val, -1);
860 uzbl.behave.modmask = 0;
861 for (i = 0; modkeys[i].key != NULL; i++) {
862 if (g_strrstr(*p, modkeys[i].key))
863 uzbl.behave.modmask |= modkeys[i].mask;
866 else if(var_is("useragent", name)) {
867 if(*p) free(*p);
868 *p = set_useragent(g_strdup(val));
870 /* variables that take int values */
871 else {
872 int *ip = (int *)p;
873 *ip = (int)strtoul(val, &endp, 10);
875 if(var_is("show_status", name)) {
876 cmd_set_status();
878 else if(var_is("always_insert_mode", name)) {
879 uzbl.behave.insert_mode =
880 uzbl.behave.always_insert_mode ? TRUE : FALSE;
881 update_title();
883 else if (var_is("max_conns", name)) {
884 g_object_set(G_OBJECT(uzbl.net.soup_session),
885 SOUP_SESSION_MAX_CONNS, uzbl.net.max_conns, NULL);
887 else if (var_is("max_conns_host", name)) {
888 g_object_set(G_OBJECT(uzbl.net.soup_session),
889 SOUP_SESSION_MAX_CONNS_PER_HOST, uzbl.net.max_conns_host, NULL);
891 else if (var_is("http_debug", name)) {
892 //soup_session_remove_feature
893 // (uzbl.net.soup_session, uzbl.net.soup_logger);
894 soup_session_remove_feature
895 (uzbl.net.soup_session, SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
896 /* do we leak if this doesn't get freed? why does it occasionally crash if freed? */
897 /*g_free(uzbl.net.soup_logger);*/
899 uzbl.net.soup_logger = soup_logger_new(uzbl.behave.http_debug, -1);
900 soup_session_add_feature(uzbl.net.soup_session,
901 SOUP_SESSION_FEATURE(uzbl.net.soup_logger));
903 else if (var_is("status_top", name)) {
904 move_statusbar();
908 return TRUE;
911 static void
912 runcmd(WebKitWebView* page, const char *param) {
913 (void) page;
914 parse_cmd_line(param);
917 static void
918 parse_cmd_line(const char *ctl_line) {
919 gchar **tokens;
921 /* SET command */
922 if(ctl_line[0] == 's' || ctl_line[0] == 'S') {
923 tokens = g_regex_split(uzbl.comm.set_regex, ctl_line, 0);
924 if(tokens[0][0] == 0) {
925 set_var_value(tokens[1], tokens[2]);
926 g_strfreev(tokens);
928 else
929 printf("Error in command: %s\n", tokens[0]);
931 /* GET command */
932 else if(ctl_line[0] == 'g' || ctl_line[0] == 'G') {
933 tokens = g_regex_split(uzbl.comm.get_regex, ctl_line, 0);
934 if(tokens[0][0] == 0) {
935 get_var_value(tokens[1]);
936 g_strfreev(tokens);
938 else
939 printf("Error in command: %s\n", tokens[0]);
941 /* BIND command */
942 else if(ctl_line[0] == 'b' || ctl_line[0] == 'B') {
943 tokens = g_regex_split(uzbl.comm.bind_regex, ctl_line, 0);
944 if(tokens[0][0] == 0) {
945 add_binding(tokens[1], tokens[2]);
946 g_strfreev(tokens);
948 else
949 printf("Error in command: %s\n", tokens[0]);
951 /* ACT command */
952 else if(ctl_line[0] == 'A' || ctl_line[0] == 'a') {
953 tokens = g_regex_split(uzbl.comm.act_regex, ctl_line, 0);
954 if(tokens[0][0] == 0) {
955 parse_command(tokens[1], tokens[2]);
956 g_strfreev(tokens);
958 else
959 printf("Error in command: %s\n", tokens[0]);
961 /* KEYCMD command */
962 else if(ctl_line[0] == 'K' || ctl_line[0] == 'k') {
963 tokens = g_regex_split(uzbl.comm.keycmd_regex, ctl_line, 0);
964 if(tokens[0][0] == 0) {
965 /* should incremental commands want each individual "keystroke"
966 sent in a loop or the whole string in one go like now? */
967 g_string_assign(uzbl.state.keycmd, tokens[1]);
968 run_keycmd(FALSE);
969 update_title();
970 g_strfreev(tokens);
973 /* Comments */
974 else if( (ctl_line[0] == '#')
975 || (ctl_line[0] == ' ')
976 || (ctl_line[0] == '\n'))
977 ; /* ignore these lines */
978 else
979 printf("Command not understood (%s)\n", ctl_line);
981 return;
984 static gchar*
985 build_stream_name(int type, const gchar* dir) {
986 char *xwin_str;
987 State *s = &uzbl.state;
988 gchar *str;
990 xwin_str = itos((int)uzbl.xwin);
991 if (type == FIFO) {
992 str = g_strdup_printf
993 ("%s/uzbl_fifo_%s", dir,
994 s->instance_name ? s->instance_name : xwin_str);
995 } else if (type == SOCKET) {
996 str = g_strdup_printf
997 ("%s/uzbl_socket_%s", dir,
998 s->instance_name ? s->instance_name : xwin_str );
1000 g_free(xwin_str);
1001 return str;
1004 static gboolean
1005 control_fifo(GIOChannel *gio, GIOCondition condition) {
1006 printf("triggered\n");
1007 gchar *ctl_line;
1008 GIOStatus ret;
1009 GError *err = NULL;
1011 if (condition & G_IO_HUP)
1012 g_error ("Fifo: Read end of pipe died!\n");
1014 if(!gio)
1015 g_error ("Fifo: GIOChannel broke\n");
1017 ret = g_io_channel_read_line(gio, &ctl_line, NULL, NULL, &err);
1018 if (ret == G_IO_STATUS_ERROR)
1019 g_error ("Fifo: Error reading: %s\n", err->message);
1021 parse_cmd_line(ctl_line);
1022 g_free(ctl_line);
1024 return TRUE;
1027 static gchar*
1028 init_fifo(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1029 if (uzbl.comm.fifo_path) { /* get rid of the old fifo if one exists */
1030 if (unlink(uzbl.comm.fifo_path) == -1)
1031 g_warning ("Fifo: Can't unlink old fifo at %s\n", uzbl.comm.fifo_path);
1032 g_free(uzbl.comm.fifo_path);
1033 uzbl.comm.fifo_path = NULL;
1036 if (*dir == ' ') { /* space unsets the variable */
1037 g_free(dir);
1038 return NULL;
1041 GIOChannel *chan = NULL;
1042 GError *error = NULL;
1043 gchar *path = build_stream_name(FIFO, dir);
1045 if (!file_exists(path)) {
1046 if (mkfifo (path, 0666) == 0) {
1047 // 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.
1048 chan = g_io_channel_new_file(path, "r+", &error);
1049 if (chan) {
1050 if (g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_fifo, NULL)) {
1051 printf ("init_fifo: created successfully as %s\n", path);
1052 uzbl.comm.fifo_path = path;
1053 return dir;
1054 } else g_warning ("init_fifo: could not add watch on %s\n", path);
1055 } else g_warning ("init_fifo: can't open: %s\n", error->message);
1056 } else g_warning ("init_fifo: can't create %s: %s\n", path, strerror(errno));
1057 } else g_warning ("init_fifo: can't create %s: file exists\n", path);
1059 /* if we got this far, there was an error; cleanup */
1060 g_free(path);
1061 g_free(dir);
1062 return NULL;
1065 static gboolean
1066 control_stdin(GIOChannel *gio, GIOCondition condition) {
1067 gchar *ctl_line = NULL;
1068 gsize ctl_line_len = 0;
1069 GIOStatus ret;
1070 GError *err = NULL;
1072 if (condition & G_IO_HUP) {
1073 ret = g_io_channel_shutdown (gio, FALSE, &err);
1074 return FALSE;
1077 ret = g_io_channel_read_line(gio, &ctl_line, &ctl_line_len, NULL, &err);
1078 if ( (ret == G_IO_STATUS_ERROR) || (ret == G_IO_STATUS_EOF) )
1079 return FALSE;
1081 parse_cmd_line(ctl_line);
1082 g_free(ctl_line);
1084 return TRUE;
1087 static void
1088 create_stdin () {
1089 GIOChannel *chan = NULL;
1090 GError *error = NULL;
1092 chan = g_io_channel_unix_new(fileno(stdin));
1093 if (chan) {
1094 if (!g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_stdin, NULL)) {
1095 g_error ("Stdin: could not add watch\n");
1096 } else {
1097 printf ("Stdin: watch added successfully\n");
1099 } else {
1100 g_error ("Stdin: Error while opening: %s\n", error->message);
1104 static gboolean
1105 control_socket(GIOChannel *chan) {
1106 struct sockaddr_un remote;
1107 char buffer[512], *ctl_line;
1108 char temp[128];
1109 int sock, clientsock, n, done;
1110 unsigned int t;
1112 sock = g_io_channel_unix_get_fd(chan);
1114 memset (buffer, 0, sizeof (buffer));
1116 t = sizeof (remote);
1117 clientsock = accept (sock, (struct sockaddr *) &remote, &t);
1119 done = 0;
1120 do {
1121 memset (temp, 0, sizeof (temp));
1122 n = recv (clientsock, temp, 128, 0);
1123 if (n == 0) {
1124 buffer[strlen (buffer)] = '\0';
1125 done = 1;
1127 if (!done)
1128 strcat (buffer, temp);
1129 } while (!done);
1131 if (strcmp (buffer, "\n") < 0) {
1132 buffer[strlen (buffer) - 1] = '\0';
1133 } else {
1134 buffer[strlen (buffer)] = '\0';
1136 close (clientsock);
1137 ctl_line = g_strdup(buffer);
1138 parse_cmd_line (ctl_line);
1141 TODO: we should be able to do it with this. but glib errors out with "Invalid argument"
1142 GError *error = NULL;
1143 gsize len;
1144 GIOStatus ret;
1145 ret = g_io_channel_read_line(chan, &ctl_line, &len, NULL, &error);
1146 if (ret == G_IO_STATUS_ERROR)
1147 g_error ("Error reading: %s\n", error->message);
1149 printf("Got line %s (%u bytes) \n",ctl_line, len);
1150 if(ctl_line) {
1151 parse_line(ctl_line);
1154 g_free(ctl_line);
1155 return TRUE;
1158 static gchar*
1159 init_socket(gchar *dir) { /* return dir or, on error, free dir and return NULL */
1160 if (uzbl.comm.socket_path) { /* remove an existing socket should one exist */
1161 if (unlink(uzbl.comm.socket_path) == -1)
1162 g_warning ("init_socket: couldn't unlink socket at %s\n", uzbl.comm.socket_path);
1163 g_free(uzbl.comm.socket_path);
1164 uzbl.comm.socket_path = NULL;
1167 if (*dir == ' ') {
1168 g_free(dir);
1169 return NULL;
1172 GIOChannel *chan = NULL;
1173 int sock, len;
1174 struct sockaddr_un local;
1175 gchar *path = build_stream_name(SOCKET, dir);
1177 sock = socket (AF_UNIX, SOCK_STREAM, 0);
1179 local.sun_family = AF_UNIX;
1180 strcpy (local.sun_path, path);
1181 unlink (local.sun_path);
1183 len = strlen (local.sun_path) + sizeof (local.sun_family);
1184 if (bind (sock, (struct sockaddr *) &local, len) != -1) {
1185 printf ("init_socket: opened in %s\n", path);
1186 listen (sock, 5);
1188 if( (chan = g_io_channel_unix_new(sock)) ) {
1189 g_io_add_watch(chan, G_IO_IN|G_IO_HUP, (GIOFunc) control_socket, chan);
1190 uzbl.comm.socket_path = path;
1191 return dir;
1193 } else g_warning ("init_socket: could not open in %s: %s\n", path, strerror(errno));
1195 /* if we got this far, there was an error; cleanup */
1196 g_free(path);
1197 g_free(dir);
1198 return NULL;
1201 static void
1202 update_title (void) {
1203 GString* string_long = g_string_new ("");
1204 GString* string_short = g_string_new ("");
1205 char* iname = NULL;
1206 gchar *statln;
1207 int iname_len;
1208 State *s = &uzbl.state;
1209 Behaviour *b = &uzbl.behave;
1211 if(s->instance_name) {
1212 iname_len = strlen(s->instance_name)+4;
1213 iname = malloc(iname_len);
1214 snprintf(iname, iname_len, "<%s> ", s->instance_name);
1216 g_string_prepend(string_long, iname);
1217 g_string_prepend(string_short, iname);
1218 free(iname);
1221 g_string_append_printf(string_long, "%s ", s->keycmd->str);
1222 if (!b->always_insert_mode)
1223 g_string_append (string_long, (b->insert_mode ? "[I] " : "[C] "));
1224 if (uzbl.gui.main_title) {
1225 g_string_append (string_long, uzbl.gui.main_title);
1226 g_string_append (string_short, uzbl.gui.main_title);
1228 g_string_append (string_long, " - Uzbl browser");
1229 g_string_append (string_short, " - Uzbl browser");
1230 if (s->selected_url[0]!=0) {
1231 g_string_append_printf (string_long, " -> (%s)", s->selected_url);
1234 gchar* title_long = g_string_free (string_long, FALSE);
1235 gchar* title_short = g_string_free (string_short, FALSE);
1237 if (b->show_status) {
1238 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), title_short);
1239 // TODO: we should probably not do this every time we want to update the title..?
1240 statln = expand_template(uzbl.behave.status_format);
1241 gtk_label_set_markup(GTK_LABEL(uzbl.gui.mainbar_label), statln);
1242 if (b->status_background) {
1243 GdkColor color;
1244 gdk_color_parse (b->status_background, &color);
1245 //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)
1246 gtk_widget_modify_bg (uzbl.gui.main_window, GTK_STATE_NORMAL, &color);
1248 g_free(statln);
1249 } else {
1250 gtk_window_set_title (GTK_WINDOW(uzbl.gui.main_window), title_long);
1253 g_free (title_long);
1254 g_free (title_short);
1257 static gboolean
1258 key_press_cb (WebKitWebView* page, GdkEventKey* event)
1260 //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
1262 (void) page;
1264 if (event->type != GDK_KEY_PRESS || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down
1265 || 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)
1266 return FALSE;
1268 /* turn off insert mode (if always_insert_mode is not used) */
1269 if (uzbl.behave.insert_mode && (event->keyval == GDK_Escape)) {
1270 uzbl.behave.insert_mode = uzbl.behave.always_insert_mode;
1271 update_title();
1272 return TRUE;
1275 if (uzbl.behave.insert_mode && (((event->state & uzbl.behave.modmask) != uzbl.behave.modmask) || (!uzbl.behave.modmask)))
1276 return FALSE;
1278 if (event->keyval == GDK_Escape) {
1279 g_string_truncate(uzbl.state.keycmd, 0);
1280 update_title();
1281 return TRUE;
1284 //Insert without shift - insert from clipboard; Insert with shift - insert from primary
1285 if (event->keyval == GDK_Insert) {
1286 gchar * str;
1287 if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) {
1288 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY));
1289 } else {
1290 str = gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1292 if (str) {
1293 g_string_append (uzbl.state.keycmd, str);
1294 update_title ();
1295 free (str);
1297 return TRUE;
1300 if ((event->keyval == GDK_BackSpace) && (uzbl.state.keycmd->len > 0)) {
1301 g_string_truncate(uzbl.state.keycmd, uzbl.state.keycmd->len - 1);
1302 update_title();
1305 gboolean key_ret = FALSE;
1306 if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter))
1307 key_ret = TRUE;
1308 if (!key_ret) g_string_append(uzbl.state.keycmd, event->string);
1310 run_keycmd(key_ret);
1311 update_title();
1312 if (key_ret) return (!uzbl.behave.insert_mode);
1313 return TRUE;
1316 static void
1317 run_keycmd(const gboolean key_ret) {
1318 /* run the keycmd immediately if it isn't incremental and doesn't take args */
1319 Action *action;
1320 if ((action = g_hash_table_lookup(uzbl.bindings, uzbl.state.keycmd->str))) {
1321 g_string_truncate(uzbl.state.keycmd, 0);
1322 parse_command(action->name, action->param);
1323 return;
1326 /* try if it's an incremental keycmd or one that takes args, and run it */
1327 GString* short_keys = g_string_new ("");
1328 GString* short_keys_inc = g_string_new ("");
1329 unsigned int i;
1330 for (i=0; i<(uzbl.state.keycmd->len); i++) {
1331 g_string_append_c(short_keys, uzbl.state.keycmd->str[i]);
1332 g_string_assign(short_keys_inc, short_keys->str);
1333 g_string_append_c(short_keys, '_');
1334 g_string_append_c(short_keys_inc, '*');
1336 gboolean exec_now = FALSE;
1337 if ((action = g_hash_table_lookup(uzbl.bindings, short_keys->str))) {
1338 if (key_ret) exec_now = TRUE; /* run normal cmds only if return was pressed */
1339 } else if ((action = g_hash_table_lookup(uzbl.bindings, short_keys_inc->str))) {
1340 if (key_ret) { /* just quit the incremental command on return */
1341 g_string_truncate(uzbl.state.keycmd, 0);
1342 break;
1343 } else exec_now = TRUE; /* always exec incr. commands on keys other than return */
1346 if (exec_now) {
1347 GString* parampart = g_string_new (uzbl.state.keycmd->str);
1348 GString* actionname = g_string_new ("");
1349 GString* actionparam = g_string_new ("");
1350 g_string_erase (parampart, 0, i+1);
1351 if (action->name)
1352 g_string_printf (actionname, action->name, parampart->str);
1353 if (action->param)
1354 g_string_printf (actionparam, action->param, parampart->str);
1355 parse_command(actionname->str, actionparam->str);
1356 g_string_free (actionname, TRUE);
1357 g_string_free (actionparam, TRUE);
1358 g_string_free (parampart, TRUE);
1359 if (key_ret)
1360 g_string_truncate(uzbl.state.keycmd, 0);
1361 break;
1364 g_string_truncate(short_keys, short_keys->len - 1);
1366 g_string_free (short_keys, TRUE);
1367 g_string_free (short_keys_inc, TRUE);
1370 static GtkWidget*
1371 create_browser () {
1372 GUI *g = &uzbl.gui;
1374 GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1375 //main_window_ref = g_object_ref(scrolled_window);
1376 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
1378 g->web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
1379 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (g->web_view));
1381 g_signal_connect (G_OBJECT (g->web_view), "title-changed", G_CALLBACK (title_change_cb), g->web_view);
1382 g_signal_connect (G_OBJECT (g->web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), g->web_view);
1383 g_signal_connect (G_OBJECT (g->web_view), "load-committed", G_CALLBACK (load_commit_cb), g->web_view);
1384 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (log_history_cb), g->web_view);
1385 g_signal_connect (G_OBJECT (g->web_view), "load-finished", G_CALLBACK (load_finish_cb), g->web_view);
1386 g_signal_connect (G_OBJECT (g->web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), g->web_view);
1387 g_signal_connect (G_OBJECT (g->web_view), "key-press-event", G_CALLBACK (key_press_cb), g->web_view);
1388 g_signal_connect (G_OBJECT (g->web_view), "new-window-policy-decision-requested", G_CALLBACK (new_window_cb), g->web_view);
1389 g_signal_connect (G_OBJECT (g->web_view), "download-requested", G_CALLBACK (download_cb), g->web_view);
1390 g_signal_connect (G_OBJECT (g->web_view), "create-web-view", G_CALLBACK (create_web_view_cb), g->web_view);
1392 return scrolled_window;
1395 static GtkWidget*
1396 create_mainbar () {
1397 GUI *g = &uzbl.gui;
1399 g->mainbar = gtk_hbox_new (FALSE, 0);
1401 /* keep a reference to the bar so we can re-pack it at runtime*/
1402 //sbar_ref = g_object_ref(g->mainbar);
1404 g->mainbar_label = gtk_label_new ("");
1405 gtk_label_set_selectable((GtkLabel *)g->mainbar_label, TRUE);
1406 gtk_label_set_ellipsize(GTK_LABEL(g->mainbar_label), PANGO_ELLIPSIZE_END);
1407 gtk_misc_set_alignment (GTK_MISC(g->mainbar_label), 0, 0);
1408 gtk_misc_set_padding (GTK_MISC(g->mainbar_label), 2, 2);
1409 gtk_box_pack_start (GTK_BOX (g->mainbar), g->mainbar_label, TRUE, TRUE, 0);
1410 return g->mainbar;
1413 static
1414 GtkWidget* create_window () {
1415 GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1416 gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
1417 gtk_widget_set_name (window, "Uzbl browser");
1418 g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
1420 return window;
1423 static void
1424 add_binding (const gchar *key, const gchar *act) {
1425 char **parts = g_strsplit(act, " ", 2);
1426 Action *action;
1428 if (!parts)
1429 return;
1431 //Debug:
1432 printf ("Binding %-10s : %s\n", key, act);
1433 action = new_action(parts[0], parts[1]);
1435 if(g_hash_table_lookup(uzbl.bindings, key))
1436 g_hash_table_remove(uzbl.bindings, key);
1437 g_hash_table_insert(uzbl.bindings, g_strdup(key), action);
1439 g_strfreev(parts);
1442 static void
1443 settings_init () {
1444 char *saveptr;
1445 State *s = &uzbl.state;
1446 Network *n = &uzbl.net;
1448 uzbl.behave.reset_command_mode = 1;
1450 if (!s->config_file) {
1451 const char* XDG_CONFIG_HOME = getenv ("XDG_CONFIG_HOME");
1452 if (! XDG_CONFIG_HOME || ! strcmp (XDG_CONFIG_HOME, "")) {
1453 XDG_CONFIG_HOME = (char*)XDG_CONFIG_HOME_default;
1455 printf("XDG_CONFIG_HOME: %s\n", XDG_CONFIG_HOME);
1457 strcpy (s->config_file_path, XDG_CONFIG_HOME);
1458 strcat (s->config_file_path, "/uzbl/config");
1459 if (file_exists (s->config_file_path)) {
1460 printf ("Config file %s found.\n", s->config_file_path);
1461 s->config_file = &s->config_file_path[0];
1462 } else {
1463 // Now we check $XDG_CONFIG_DIRS
1464 char *XDG_CONFIG_DIRS = getenv ("XDG_CONFIG_DIRS");
1465 if (! XDG_CONFIG_DIRS || ! strcmp (XDG_CONFIG_DIRS, ""))
1466 XDG_CONFIG_DIRS = XDG_CONFIG_DIRS_default;
1468 printf("XDG_CONFIG_DIRS: %s\n", XDG_CONFIG_DIRS);
1470 char buffer[512];
1471 strcpy (buffer, XDG_CONFIG_DIRS);
1472 const gchar* dir = (char *) strtok_r (buffer, ":", &saveptr);
1473 while (dir && ! file_exists (s->config_file_path)) {
1474 strcpy (s->config_file_path, dir);
1475 strcat (s->config_file_path, "/uzbl/config_file_pathig");
1476 if (file_exists (s->config_file_path)) {
1477 printf ("Config file %s found.\n", s->config_file_path);
1478 s->config_file = &s->config_file_path[0];
1480 dir = (char * ) strtok_r (NULL, ":", &saveptr);
1485 if (s->config_file) {
1486 GIOChannel *chan = NULL;
1487 GError *error = NULL;
1488 gchar *readbuf = NULL;
1489 gsize len;
1491 chan = g_io_channel_new_file(s->config_file, "r", &error);
1493 if (chan) {
1494 while (g_io_channel_read_line(chan, &readbuf, &len, NULL, NULL)
1495 == G_IO_STATUS_NORMAL) {
1496 parse_cmd_line(readbuf);
1497 g_free (readbuf);
1500 g_io_channel_unref (chan);
1501 printf ("Config %s loaded\n", s->config_file);
1502 } else {
1503 fprintf(stderr, "uzbl: error loading file%s\n", s->config_file);
1505 } else {
1506 printf ("No configuration file loaded.\n");
1508 if (!uzbl.behave.status_format)
1509 uzbl.behave.status_format = g_strdup(STATUS_DEFAULT);
1511 g_signal_connect(n->soup_session, "request-queued", G_CALLBACK(handle_cookies), NULL);
1514 static gchar*
1515 set_useragent(gchar *val) {
1516 if (*val == ' ') {
1517 g_free(val);
1518 return NULL;
1520 gchar *ua = expand_template(val);
1521 if (ua)
1522 g_object_set(G_OBJECT(uzbl.net.soup_session), SOUP_SESSION_USER_AGENT, ua, NULL);
1523 return ua;
1526 static void handle_cookies (SoupSession *session, SoupMessage *msg, gpointer user_data){
1527 (void) session;
1528 (void) user_data;
1529 if (!uzbl.behave.cookie_handler) return;
1531 gchar * stdout = NULL;
1532 soup_message_add_header_handler(msg, "got-headers", "Set-Cookie", G_CALLBACK(save_cookies), NULL);
1533 GString* args = g_string_new ("");
1534 SoupURI * soup_uri = soup_message_get_uri(msg);
1535 g_string_printf (args, "GET %s %s", soup_uri->host, soup_uri->path);
1536 run_command(uzbl.behave.cookie_handler, args->str, TRUE, &stdout);
1537 if(stdout) {
1538 soup_message_headers_replace (msg->request_headers, "Cookie", stdout);
1540 g_string_free(args, TRUE);
1543 static void
1544 save_cookies (SoupMessage *msg, gpointer user_data){
1545 (void) user_data;
1546 GSList *ck;
1547 char *cookie;
1548 for (ck = soup_cookies_from_response(msg); ck; ck = ck->next){
1549 cookie = soup_cookie_to_set_cookie_header(ck->data);
1550 GString* args = g_string_new ("");
1551 SoupURI * soup_uri = soup_message_get_uri(msg);
1552 g_string_printf (args, "PUT %s %s \"%s\"", soup_uri->host, soup_uri->path, cookie);
1553 run_command(uzbl.behave.cookie_handler, args->str, FALSE, NULL);
1554 g_string_free(args, TRUE);
1555 free(cookie);
1557 g_slist_free(ck);
1562 main (int argc, char* argv[]) {
1563 gtk_init (&argc, &argv);
1564 if (!g_thread_supported ())
1565 g_thread_init (NULL);
1567 printf("Uzbl start location: %s\n", argv[0]);
1568 strcpy(uzbl.state.executable_path,argv[0]);
1570 strcat ((char *) XDG_CONFIG_HOME_default, getenv ("HOME"));
1571 strcat ((char *) XDG_CONFIG_HOME_default, "/.config");
1573 GError *error = NULL;
1574 GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
1575 g_option_context_add_main_entries (context, entries, NULL);
1576 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1577 g_option_context_parse (context, &argc, &argv, &error);
1578 /* initialize hash table */
1579 uzbl.bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_action);
1581 uzbl.net.soup_session = webkit_get_default_session();
1582 uzbl.state.keycmd = g_string_new("");
1584 if(setup_signal(SIGTERM, catch_sigterm) == SIG_ERR)
1585 fprintf(stderr, "uzbl: error hooking SIGTERM\n");
1586 if(setup_signal(SIGINT, catch_sigint) == SIG_ERR)
1587 fprintf(stderr, "uzbl: error hooking SIGINT\n");
1589 if(uname(&uzbl.state.unameinfo) == -1)
1590 g_printerr("Can't retrieve unameinfo. Your useragent might appear wrong.\n");
1592 setup_regex();
1593 setup_scanner();
1594 commands_hash ();
1595 make_var_to_name_hash();
1598 uzbl.gui.vbox = gtk_vbox_new (FALSE, 0);
1600 uzbl.gui.scrolled_win = create_browser();
1601 create_mainbar();
1603 /* initial packing */
1604 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.scrolled_win, TRUE, TRUE, 0);
1605 gtk_box_pack_start (GTK_BOX (uzbl.gui.vbox), uzbl.gui.mainbar, FALSE, TRUE, 0);
1607 uzbl.gui.main_window = create_window ();
1608 gtk_container_add (GTK_CONTAINER (uzbl.gui.main_window), uzbl.gui.vbox);
1610 load_uri (uzbl.gui.web_view, uzbl.state.uri); //TODO: is this needed?
1612 gtk_widget_grab_focus (GTK_WIDGET (uzbl.gui.web_view));
1613 gtk_widget_show_all (uzbl.gui.main_window);
1614 uzbl.xwin = GDK_WINDOW_XID (GTK_WIDGET (uzbl.gui.main_window)->window);
1615 printf("window_id %i\n",(int) uzbl.xwin);
1616 printf("pid %i\n", getpid ());
1617 printf("name: %s\n", uzbl.state.instance_name);
1619 uzbl.gui.scbar_v = (GtkScrollbar*) gtk_vscrollbar_new (NULL);
1620 uzbl.gui.bar_v = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_v);
1621 uzbl.gui.scbar_h = (GtkScrollbar*) gtk_hscrollbar_new (NULL);
1622 uzbl.gui.bar_h = gtk_range_get_adjustment((GtkRange*) uzbl.gui.scbar_h);
1623 gtk_widget_set_scroll_adjustments ((GtkWidget*) uzbl.gui.web_view, uzbl.gui.bar_h, uzbl.gui.bar_v);
1625 settings_init ();
1627 if (!uzbl.behave.show_status)
1628 gtk_widget_hide(uzbl.gui.mainbar);
1629 else
1630 update_title();
1632 create_stdin();
1634 gtk_main ();
1635 clean_up();
1637 return EXIT_SUCCESS;
1640 /* vi: set et ts=4: */