version 1.3.0
[vimprobable2.git] / main.c
blob40c6e8d851ea358eac957fe6aa40ab767d26c920
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2013 by Hannes Schueller
4 (c) 2009-2010 by Matto Fransen
5 (c) 2010-2011 by Hans-Peter Deifel
6 (c) 2010-2011 by Thomas Adam
7 (c) 2011 by Albert Kim
8 (c) 2011-2013 by Daniel Carl
9 (c) 2012 by Matthew Carter
10 see LICENSE file
13 #include <X11/Xlib.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include "includes.h"
20 #include "vimprobable.h"
21 #include "utilities.h"
22 #include "callbacks.h"
23 #include "javascript.h"
25 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
26 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
27 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
29 /* remove unused bits, numlock symbol and buttons from keymask */
30 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
32 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
33 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
34 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
36 /* callbacks here */
37 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
38 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
39 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
40 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
41 static WebKitWebView* inspector_new_cb(WebKitWebInspector* inspector, WebKitWebView* web_view);
42 static gboolean inspector_show_cb(WebKitWebInspector *inspector);
43 static gboolean inspector_close_cb(WebKitWebInspector *inspector);
44 static void inspector_finished_cb(WebKitWebInspector *inspector);
45 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
46 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
47 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
48 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
49 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
50 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
51 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
52 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
53 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
54 static void webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec);
55 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
56 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
57 static WebKitWebView* webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
58 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
59 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
60 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
61 static gboolean blank_cb(void);
63 /* functions */
64 static gboolean bookmark(const Arg *arg);
65 static gboolean browser_settings(const Arg *arg);
66 static gboolean commandhistoryfetch(const Arg *arg);
67 static gboolean complete(const Arg *arg);
68 static gboolean descend(const Arg *arg);
69 gboolean echo(const Arg *arg);
70 static gboolean focus_input(const Arg *arg);
71 static gboolean open_editor(const Arg *arg);
72 void _resume_from_editor(GPid child_pid, int status, gpointer data);
73 static gboolean input(const Arg *arg);
74 static gboolean open_inspector(const Arg * arg);
75 static gboolean navigate(const Arg *arg);
76 static gboolean number(const Arg *arg);
77 static gboolean open_arg(const Arg *arg);
78 static gboolean open_remembered(const Arg *arg);
79 static gboolean paste(const Arg *arg);
80 static gboolean quickmark(const Arg *arg);
81 static gboolean quit(const Arg *arg);
82 static gboolean revive(const Arg *arg);
83 static gboolean print_frame(const Arg *arg);
84 static gboolean search(const Arg *arg);
85 static gboolean set(const Arg *arg);
86 static gboolean script(const Arg *arg);
87 static gboolean scroll(const Arg *arg);
88 static gboolean search_tag(const Arg *arg);
89 static gboolean yank(const Arg *arg);
90 static gboolean view_source(const Arg * arg);
91 static gboolean zoom(const Arg *arg);
92 static gboolean fake_key_event(const Arg *arg);
94 static void clear_focus(void);
95 static void update_url(const char *uri);
96 static void setup_client(void);
97 static void setup_modkeys(void);
98 static void setup_gui(void);
99 static void setup_settings(void);
100 static void setup_signals(void);
101 static void ascii_bar(int total, int state, char *string);
102 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
103 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
104 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
105 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
106 const char *bg_color_str, const char *fg_color_str);
107 static void scripts_run_user_file(void);
109 static gboolean history(void);
110 static gboolean process_set_line(char *line);
111 void save_command_history(char *line);
112 void toggle_proxy(gboolean onoff);
113 void toggle_scrollbars(gboolean onoff);
114 void set_default_winsize(const char * const size);
116 gboolean process_keypress(GdkEventKey *event);
117 void fill_suggline(char * suggline, const char * command, const char *fill_with);
118 GtkWidget * fill_eventbox(const char * completion_line);
119 static void mop_up(void);
121 #include "main.h"
123 /* variables */
124 static char **args;
126 #include "config.h"
127 #include "keymap.h"
129 /* Cookie support. */
130 #ifdef ENABLE_COOKIE_SUPPORT
131 static void setup_cookies(void);
132 static char *get_cookies(SoupURI *soup_uri);
133 static void load_all_cookies(void);
134 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
135 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
136 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
137 #endif
139 Client client;
141 /* callbacks */
142 void
143 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
144 quit(NULL);
147 void
148 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
149 gtk_window_set_title(client.gui.window, title);
152 void
153 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
154 #ifdef ENABLE_GTK_PROGRESS_BAR
155 gtk_entry_set_progress_fraction(GTK_ENTRY(client.gui.inputbox), progress == 100 ? 0 : (double)progress/100);
156 #endif
157 update_state();
160 #ifdef ENABLE_WGET_PROGRESS_BAR
161 void
162 ascii_bar(int total, int state, char *string) {
163 int i;
165 for (i = 0; i < state; i++)
166 string[i] = progressbartickchar;
167 string[i++] = progressbarcurrent;
168 for (; i < total; i++)
169 string[i] = progressbarspacer;
170 string[i] = '\0';
172 #endif
174 void
175 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
176 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
177 const char *uri = webkit_web_view_get_uri(webview);
179 update_url(uri);
180 script(&a);
181 g_free(a.s);
182 scripts_run_user_file();
184 if (client.state.mode == ModeInsert || client.state.mode == ModeHints) {
185 Arg a = { .i = ModeNormal };
186 set(&a);
188 client.state.manual_focus = FALSE;
191 void
192 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
193 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
194 gboolean scripts;
196 g_object_get(settings, "enable-scripts", &scripts, NULL);
197 if (escape_input_on_load && scripts && !client.state.manual_focus && !gtk_widget_is_focus(client.gui.inputbox)) {
198 clear_focus();
200 if (HISTORY_MAX_ENTRIES > 0)
201 history();
202 update_state();
205 void
206 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
207 /* retrieve the URI of the temporary webview */
208 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
209 /* clean up */
210 webkit_web_view_stop_loading(temp_view);
211 gtk_widget_destroy(GTK_WIDGET(temp_view));
212 /* open the requested window */
213 open_arg(&a);
216 static WebKitWebView *
217 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
218 if (client.state.rememberedURI != NULL && strlen(client.state.rememberedURI) > 0) {
219 if (strncmp(client.state.rememberedURI, "javascript:", 11) != 0) {
220 Arg a = { .i = TargetNew, .s = client.state.rememberedURI };
221 open_arg(&a);
222 return NULL;
225 /* create a temporary webview to execute the script in */
226 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
227 /* wait until the new webview receives its new URI */
228 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
229 return temp_view;
232 gboolean
233 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
234 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
235 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
236 open_arg(&a);
237 webkit_web_policy_decision_ignore(decision);
238 return TRUE;
241 gboolean
242 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
243 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
244 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
245 webkit_web_policy_decision_download(decision);
246 return TRUE;
247 } else {
248 return FALSE;
252 static WebKitWebView*
253 inspector_new_cb(WebKitWebInspector *inspector, WebKitWebView* web_view) {
254 return WEBKIT_WEB_VIEW(webkit_web_view_new());
257 static gboolean
258 inspector_show_cb(WebKitWebInspector *inspector) {
259 WebKitWebView *webview;
260 State *state = &client.state;
262 if (state->is_inspecting) {
263 return FALSE;
266 webview = webkit_web_inspector_get_web_view(inspector);
267 gtk_paned_pack2(GTK_PANED(client.gui.pane), GTK_WIDGET(webview), TRUE, TRUE);
268 gtk_widget_show(GTK_WIDGET(webview));
270 state->is_inspecting = TRUE;
272 return TRUE;
275 static gboolean
276 inspector_close_cb(WebKitWebInspector *inspector) {
277 GtkWidget *widget;
278 State *state = &client.state;
280 if (!state->is_inspecting) {
281 return FALSE;
283 widget = GTK_WIDGET(webkit_web_inspector_get_web_view(inspector));
284 gtk_widget_hide(widget);
285 gtk_widget_destroy(widget);
287 state->is_inspecting = FALSE;
289 return TRUE;
292 static void
293 inspector_finished_cb(WebKitWebInspector *inspector) {
294 g_free(inspector);
297 gboolean
298 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
299 const gchar *filename;
300 gchar *uri, *path;
301 uint32_t size;
302 WebKitDownloadStatus status;
304 filename = webkit_download_get_suggested_filename(download);
305 if (filename == NULL || strlen(filename) == 0) {
306 filename = "vimprobable_download";
308 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
309 uri = g_strconcat("file://", path, NULL);
310 webkit_download_set_destination_uri(download, uri);
311 g_free(path);
312 g_free(uri);
313 size = (uint32_t)webkit_download_get_total_size(download);
314 if (size > 0)
315 echo_message(Info, "Download %s started (expected size: %u bytes)...", filename, size);
316 else
317 echo_message(Info, "Download %s started (unknown size)...", filename);
318 client.state.activeDownloads = g_list_prepend(client.state.activeDownloads, download);
319 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
320 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
321 status = webkit_download_get_status(download);
322 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
323 webkit_download_start(download);
324 update_state();
325 return TRUE;
328 gboolean
329 blank_cb(void) {
330 return TRUE;
333 void
334 download_progress(WebKitDownload *d, GParamSpec *pspec) {
335 WebKitDownloadStatus status = webkit_download_get_status(d);
337 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
338 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
339 echo_message(Error, "Error while downloading %s", webkit_download_get_suggested_filename(d));
340 } else {
341 echo_message(Info, "Download %s finished", webkit_download_get_suggested_filename(d));
343 client.state.activeDownloads = g_list_remove(client.state.activeDownloads, d);
345 update_state();
349 gboolean
350 process_keypress(GdkEventKey *event) {
351 State *state = &client.state;
352 KeyList *current;
353 guint keyval;
354 GdkModifierType irrelevant;
356 /* Get a mask of modifiers that shouldn't be considered for this event.
357 * E.g.: It shouldn't matter whether ';' is shifted or not. */
358 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
359 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
361 current = client.config.keylistroot;
363 while (current != NULL) {
364 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
365 && (current->Element.modkey == state->current_modkey
366 || (!current->Element.modkey && !state->current_modkey)
367 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
368 && current->Element.key == keyval
369 && current->Element.func)
370 if (current->Element.func(&current->Element.arg)) {
371 state->current_modkey = state->count = 0;
372 update_state();
373 return TRUE;
375 current = current->next;
377 return FALSE;
380 gboolean
381 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
382 State *state = &client.state;
383 Arg a = { .i = ModeNormal, .s = NULL };
384 guint keyval;
385 GdkModifierType irrelevant;
387 /* Get a mask of modifiers that shouldn't be considered for this event.
388 * E.g.: It shouldn't matter whether ';' is shifted or not. */
389 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
390 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
392 switch (state->mode) {
393 case ModeNormal:
394 if ((CLEAN(event->state) & ~irrelevant) == 0) {
395 if (IS_ESCAPE(event)) {
396 echo_message(Info, "");
397 g_free(a.s);
398 } else if (state->current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
399 || (event->keyval == GDK_0 && state->count))) {
400 state->count = (state->count ? state->count * 10 : 0) + (event->keyval - GDK_0);
401 update_state();
402 return TRUE;
403 } else if (strchr(client.config.modkeys, event->keyval) && state->current_modkey != event->keyval) {
404 state->current_modkey = event->keyval;
405 update_state();
406 return TRUE;
409 /* keybindings */
410 if (process_keypress(event) == TRUE) return TRUE;
412 break;
413 case ModeInsert:
414 if (IS_ESCAPE(event)) {
415 a.i = Silent;
416 a.s = g_strdup("hints.clearFocus();");
417 script(&a);
418 g_free(a.s);
419 a.i = ModeNormal;
420 return set(&a);
421 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
422 /* keybindings of non-printable characters */
423 if (process_keypress(event) == TRUE) return TRUE;
425 case ModePassThrough:
426 if (IS_ESCAPE(event)) {
427 echo_message(Info, "");
428 set(&a);
429 return TRUE;
431 break;
432 case ModeSendKey:
433 echo_message(Info, "");
434 set(&a);
435 break;
437 return FALSE;
440 void
441 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
442 const char *fg_color_str) {
443 GdkColor fg_color;
444 GdkColor bg_color;
445 PangoFontDescription *font;
447 font = pango_font_description_from_string(font_str);
448 gtk_widget_modify_font(widget, font);
449 pango_font_description_free(font);
451 if (fg_color_str)
452 gdk_color_parse(fg_color_str, &fg_color);
453 if (bg_color_str)
454 gdk_color_parse(bg_color_str, &bg_color);
456 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
457 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
459 return;
462 void
463 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
464 const char *uri = webkit_web_view_get_uri(webview);
465 char *markup;
467 memset(client.state.rememberedURI, 0, BUF_SIZE);
468 if (link) {
469 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
470 gtk_label_set_markup(GTK_LABEL(client.gui.status_url), markup);
471 strncpy(client.state.rememberedURI, link, BUF_SIZE);
472 g_free(markup);
473 } else
474 update_url(uri);
477 gboolean
478 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
479 Arg a;
481 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
482 if (gtk_window_has_toplevel_focus(client.gui.window)) {
483 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
484 a.i = ModeNormal;
485 return set(&a);
486 } else if (!strcmp(message, "insertmode_on")) {
487 a.i = ModeInsert;
488 return set(&a);
491 return FALSE;
494 void
495 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
496 Gui *gui = &client.gui;
497 State *state = &client.state;
498 char *text;
499 guint16 length = gtk_entry_get_text_length(entry);
500 Arg a;
501 gboolean forward = FALSE;
503 a.i = HideCompletion;
504 complete(&a);
505 if (length == 0)
506 return;
507 text = (char*)gtk_entry_get_text(entry);
509 /* move focus from inputbox to print potential messages that could not be
510 * printed as long as the inputbox is focused */
511 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
513 if (length > 1 && text[0] == ':') {
514 process_line((text + 1));
515 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
516 webkit_web_view_unmark_text_matches(gui->webview);
517 #ifdef ENABLE_MATCH_HIGHLITING
518 webkit_web_view_mark_text_matches(gui->webview, &text[1], FALSE, 0);
519 webkit_web_view_set_highlight_text_matches(gui->webview, TRUE);
520 #endif
521 state->count = 0;
522 #ifndef ENABLE_INCREMENTAL_SEARCH
523 a.s =& text[1];
524 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
525 search(&a);
526 #else
527 state->search_direction = forward;
528 if (state->search_handle) {
529 g_free(state->search_handle);
531 state->search_handle = g_strdup(&text[1]);
532 #endif
533 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
534 a.i = Silent;
535 a.s = g_strdup_printf("hints.fire();");
536 script(&a);
537 g_free(a.s);
538 update_state();
539 } else
540 return;
541 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
544 gboolean
545 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
546 Arg a;
547 int numval;
548 State *state = &client.state;
550 if (state->mode == ModeHints) {
551 if (event->keyval == GDK_Tab) {
552 a.i = Silent;
553 a.s = g_strdup_printf("hints.focusNextHint();");
554 script(&a);
555 g_free(a.s);
556 update_state();
557 return TRUE;
559 if (event->keyval == GDK_ISO_Left_Tab) {
560 a.i = Silent;
561 a.s = g_strdup_printf("hints.focusPreviousHint();");
562 script(&a);
563 g_free(a.s);
564 update_state();
565 return TRUE;
567 if (event->keyval == GDK_Return) {
568 a.i = Silent;
569 a.s = g_strdup_printf("hints.fire();");
570 script(&a);
571 g_free(a.s);
572 update_state();
573 return TRUE;
576 switch (event->keyval) {
577 case GDK_bracketleft:
578 case GDK_Escape:
579 if (!IS_ESCAPE(event)) break;
580 a.i = HideCompletion;
581 complete(&a);
582 a.i = ModeNormal;
583 state->commandpointer = 0;
584 return set(&a);
585 break;
586 case GDK_Tab:
587 a.i = DirectionNext;
588 return complete(&a);
589 break;
590 case GDK_Up:
591 a.i = DirectionPrev;
592 return commandhistoryfetch(&a);
593 break;
594 case GDK_Down:
595 a.i = DirectionNext;
596 return commandhistoryfetch(&a);
597 break;
598 case GDK_ISO_Left_Tab:
599 a.i = DirectionPrev;
600 return complete(&a);
601 break;
604 if (state->mode == ModeHints) {
605 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
606 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
607 (event->keyval == GDK_BackSpace)) {
608 state->count /= 10;
609 a.i = Silent;
610 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
611 script(&a);
612 g_free(a.s);
613 update_state();
614 return TRUE;
617 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
618 if ((numval >= 1 && numval <= 9) || (numval == 0 && state->count)) {
619 /* allow a zero as non-first number */
620 state->count = (state->count ? state->count * 10 : 0) + numval;
621 a.i = Silent;
622 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
623 script(&a);
624 g_free(a.s);
625 update_state();
626 return TRUE;
630 return FALSE;
633 gboolean
634 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
635 int i;
636 WebKitHitTestResult *result;
637 WebKitHitTestResultContext context;
638 State *state = &client.state;
639 if (state->mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
640 /* handle mouse click events */
641 for (i = 0; i < LENGTH(mouse); i++) {
642 if (mouse[i].mask == CLEAN(event->button.state)
643 && (mouse[i].modkey == state->current_modkey
644 || (!mouse[i].modkey && !state->current_modkey)
645 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
646 && mouse[i].button == event->button.button
647 && mouse[i].func) {
648 if (mouse[i].func(&mouse[i].arg)) {
649 state->current_modkey = state->count = 0;
650 update_state();
651 return TRUE;
655 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
656 g_object_get(result, "context", &context, NULL);
657 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
658 Arg a = { .i = ModeInsert };
659 set(&a);
660 state->manual_focus = TRUE;
662 } else if (state->mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
663 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
664 g_object_get(result, "context", &context, NULL);
665 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
666 Arg a = { .i = ModeNormal };
667 set(&a);
669 } else {
670 gchar *value = NULL, *message = NULL;
671 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
672 if (value && !strcmp(value, "[object HTMLFormElement]")) {
673 Arg a = { .i = ModeInsert, .s = NULL };
674 set(&a);
675 state->manual_focus = TRUE;
677 g_free(value);
678 g_free(message);
680 return FALSE;
683 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
684 Arg a;
685 guint16 length = gtk_entry_get_text_length(entry);
687 if (!length) {
688 a.i = HideCompletion;
689 complete(&a);
690 a.i = ModeNormal;
691 return set(&a);
693 return FALSE;
696 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
697 Arg a;
698 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
699 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
700 gboolean forward = FALSE;
702 /* Update incremental search if the user changes the search text.
704 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
705 * from the user. But if the entry is focused and the text is set
706 * through gtk_entry_set_text() in some asyncrounous operation,
707 * I would consider that a bug.
710 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
711 webkit_web_view_unmark_text_matches(client.gui.webview);
712 webkit_web_view_search_text(client.gui.webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
713 return TRUE;
714 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
715 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
716 a.i = Silent;
717 switch (text[0]) {
718 case '.':
719 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
720 break;
722 case ',':
723 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
724 break;
726 case ';':
727 a.s = NULL;
728 switch (text[1]) {
729 case 's':
730 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
731 break;
732 case 'y':
733 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
734 break;
735 case 'o':
736 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
737 break;
738 case 't': case 'w':
739 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
740 break;
741 case 'O': case 'T': case 'W':
742 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
743 break;
744 case 'i':
745 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
746 break;
747 case 'I':
748 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
749 break;
751 break;
753 client.state.count = 0;
754 if (a.s) {
755 script(&a);
756 g_free(a.s);
759 return TRUE;
760 } else if (length == 0) {
761 client.state.mode = ModeNormal;
762 a.i = Silent;
763 a.s = g_strdup("hints.clearHints();");
764 script(&a);
765 g_free(a.s);
766 client.state.count = 0;
767 update_state();
770 return FALSE;
773 /* funcs here */
775 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
776 memset(suggline, 0, 512);
777 strncpy(suggline, command, 512);
778 strncat(suggline, " ", 1);
779 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
782 GtkWidget * fill_eventbox(const char * completion_line) {
783 GtkBox * row;
784 GtkWidget *row_eventbox, *el;
785 GdkColor color;
786 char *markup, *markup_tmp;
788 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
789 row_eventbox = gtk_event_box_new();
790 gdk_color_parse(completionbgcolor[0], &color);
791 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
792 el = gtk_label_new(NULL);
793 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
794 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
795 markup_tmp, "</span>", NULL);
796 gtk_label_set_markup(GTK_LABEL(el), markup);
797 g_free(markup_tmp);
798 g_free(markup);
799 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
800 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
801 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
802 return row_eventbox;
805 gboolean
806 complete(const Arg *arg) {
807 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
808 size_t listlen, len, cmdlen;
809 int i, spacepos;
810 Listelement *elementlist = NULL, *elementpointer;
811 gboolean highlight = FALSE;
812 GtkBox *row;
813 GtkWidget *row_eventbox, *el;
814 GtkBox *_table;
815 GdkColor color;
816 static GtkWidget *table, *top_border;
817 static char *prefix;
818 static char **suggestions;
819 static GtkWidget **widgets;
820 static int n = 0, m, current = -1;
821 Gui *gui = &client.gui;
823 str = (char*)gtk_entry_get_text(GTK_ENTRY(gui->inputbox));
824 len = strlen(str);
826 /* Get the length of the list of commands for completion. We need this to
827 * malloc/realloc correctly.
829 listlen = LENGTH(commands);
831 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
832 return TRUE;
833 if (prefix) {
834 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
835 gdk_color_parse(completionbgcolor[0], &color);
836 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
837 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
838 if ((arg->i == DirectionNext && current == 0)
839 || (arg->i == DirectionPrev && current == n - 1))
840 current = -1;
841 } else {
842 free(widgets);
843 free(suggestions);
844 free(prefix);
845 gtk_widget_destroy(GTK_WIDGET(table));
846 gtk_widget_destroy(GTK_WIDGET(top_border));
847 table = NULL;
848 widgets = NULL;
849 suggestions = NULL;
850 prefix = NULL;
851 n = 0;
852 current = -1;
853 if (arg->i == HideCompletion)
854 return TRUE;
856 } else if (arg->i == HideCompletion)
857 return TRUE;
858 if (!widgets) {
859 prefix = g_strdup(str);
860 widgets = malloc(sizeof(GtkWidget*) * listlen);
861 suggestions = malloc(sizeof(char*) * listlen);
862 top_border = gtk_event_box_new();
863 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
864 gdk_color_parse(completioncolor[2], &color);
865 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
866 table = gtk_event_box_new();
867 gdk_color_parse(completionbgcolor[0], &color);
868 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
869 highlight = len > 1;
870 if (strchr(str, ' ') == NULL) {
871 /* command completion */
872 listlen = LENGTH(commands);
873 for (i = 0; i < listlen; i++) {
874 if (commands[i].cmd == NULL)
875 break;
876 cmdlen = strlen(commands[i].cmd);
877 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
878 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
879 if (highlight) {
880 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
881 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
882 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
883 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
885 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
886 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
887 row_eventbox = gtk_event_box_new();
888 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
889 el = gtk_label_new(NULL);
890 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
891 free(s);
892 gtk_label_set_markup(GTK_LABEL(el), markup);
893 g_free(markup);
894 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
895 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
896 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
897 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
898 suggestions[n] = commands[i].cmd;
899 widgets[n++] = row_eventbox;
902 } else {
903 entry = (char *)malloc(512 * sizeof(char));
904 if (entry == NULL) {
905 return FALSE;
907 memset(entry, 0, 512);
908 suggurls = malloc(sizeof(char*) * listlen);
909 if (suggurls == NULL) {
910 return FALSE;
912 spacepos = strcspn(str, " ");
913 searchfor = (str + spacepos + 1);
914 strncpy(command, (str + 1), spacepos - 1);
915 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
916 /* browser settings */
917 listlen = LENGTH(browsersettings);
918 for (i = 0; i < listlen; i++) {
919 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
920 /* match */
921 fill_suggline(suggline, command, browsersettings[i].name);
922 /* FIXME(HP): This memory is never freed */
923 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
924 strncpy(suggurls[n], suggline, 512);
925 suggestions[n] = suggurls[n];
926 row_eventbox = fill_eventbox(suggline);
927 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
928 widgets[n++] = row_eventbox;
932 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
933 /* completion on tags */
934 spacepos = strcspn(str, " ");
935 searchfor = (str + spacepos + 1);
936 elementlist = complete_list(searchfor, 1, elementlist);
937 } else {
938 /* URL completion: bookmarks */
939 elementlist = complete_list(searchfor, 0, elementlist);
940 m = count_list(elementlist);
941 if (m < MAX_LIST_SIZE) {
942 /* URL completion: history */
943 elementlist = complete_list(searchfor, 2, elementlist);
946 elementpointer = elementlist;
947 while (elementpointer != NULL) {
948 fill_suggline(suggline, command, elementpointer->element);
949 /* FIXME(HP): This memory is never freed */
950 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
951 strncpy(suggurls[n], suggline, 512);
952 suggestions[n] = suggurls[n];
953 row_eventbox = fill_eventbox(suggline);
954 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
955 widgets[n++] = row_eventbox;
956 elementpointer = elementpointer->next;
957 if (n >= MAX_LIST_SIZE)
958 break;
960 free_list(elementlist);
961 if (suggurls != NULL) {
962 free(suggurls);
963 suggurls = NULL;
965 if (entry != NULL) {
966 free(entry);
967 entry = NULL;
970 /* TA: FIXME - this needs rethinking entirely. */
972 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
973 if (widgets_temp == NULL && widgets == NULL) {
974 fprintf(stderr, "Couldn't realloc() widgets\n");
975 exit(1);
977 widgets = widgets_temp;
978 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
979 if (suggestions_temp == NULL && suggestions == NULL) {
980 fprintf(stderr, "Couldn't realloc() suggestions\n");
981 exit(1);
983 suggestions = suggestions_temp;
985 if (!n) {
986 gdk_color_parse(completionbgcolor[1], &color);
987 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
988 el = gtk_label_new(NULL);
989 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
990 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
991 gtk_label_set_markup(GTK_LABEL(el), markup);
992 g_free(markup);
993 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
995 gtk_box_pack_start(gui->box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
996 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
997 gtk_box_pack_start(gui->box, GTK_WIDGET(table), FALSE, FALSE, 0);
998 gtk_widget_show_all(GTK_WIDGET(gui->window));
999 if (!n)
1000 return TRUE;
1001 current = arg->i == DirectionPrev ? n - 1 : 0;
1003 if (current != -1) {
1004 gdk_color_parse(completionbgcolor[2], &color);
1005 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1006 s = g_strconcat(":", suggestions[current], NULL);
1007 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), s);
1008 g_free(s);
1009 } else
1010 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), prefix);
1011 gtk_editable_set_position(GTK_EDITABLE(gui->inputbox), -1);
1012 return TRUE;
1015 gboolean
1016 descend(const Arg *arg) {
1017 char *source = (char*)webkit_web_view_get_uri(client.gui.webview), *p = &source[0], *new;
1018 int i, len;
1019 client.state.count = client.state.count ? client.state.count : 1;
1021 if (!source)
1022 return TRUE;
1023 if (arg->i == Rootdir) {
1024 for (i = 0; i < 3; i++) /* get to the third slash */
1025 if (!(p = strchr(++p, '/')))
1026 return TRUE; /* if we cannot find it quit */
1027 } else {
1028 len = strlen(source);
1029 if (!len) /* if string is empty quit */
1030 return TRUE;
1031 p = source + len; /* start at the end */
1032 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1033 ++client.state.count;
1034 for (i = 0; i < client.state.count; i++)
1035 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1036 if (p == source) /* if we reach the first char pointer quit */
1037 return TRUE;
1038 ++p; /* since we do p-- in the while, we are pointing at
1039 the char before the slash, so +1 */
1041 len = p - source + 1; /* new length = end - start + 1 */
1042 new = malloc(len + 1);
1043 memcpy(new, source, len);
1044 new[len] = '\0';
1045 webkit_web_view_load_uri(client.gui.webview, new);
1046 free(new);
1047 return TRUE;
1050 gboolean
1051 echo(const Arg *arg) {
1052 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1054 if (index < Info || index > Error)
1055 return TRUE;
1057 if (!gtk_widget_is_focus(GTK_WIDGET(client.gui.inputbox))) {
1058 set_widget_font_and_color(client.gui.inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1059 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), !arg->s ? "" : arg->s);
1062 return TRUE;
1065 static gboolean
1066 open_inspector(const Arg * arg) {
1067 gboolean inspect_enabled;
1068 WebKitWebSettings *settings;
1069 State *state = &client.state;
1071 settings = webkit_web_view_get_settings(client.gui.webview);
1072 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1073 if (inspect_enabled) {
1074 if (state->is_inspecting) {
1075 webkit_web_inspector_close(client.gui.inspector);
1076 } else {
1077 webkit_web_inspector_show(client.gui.inspector);
1079 return TRUE;
1080 } else {
1081 echo_message(Error, "Webinspector is not enabled");
1082 return FALSE;
1086 gboolean
1087 input(const Arg *arg) {
1088 int pos = 0;
1089 client.state.count = 0;
1090 const char *url;
1091 int index = Info;
1092 Arg a;
1093 GtkWidget *inputbox = client.gui.inputbox;
1095 /* if inputbox hidden, show it again */
1096 if (!gtk_widget_get_visible(inputbox))
1097 gtk_widget_set_visible(inputbox, TRUE);
1099 update_state();
1101 /* Set the colour and font back to the default, so that we don't still
1102 * maintain a red colour from a warning from an end of search indicator,
1103 * etc.
1105 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1107 /* to avoid things like :open URL :open URL2 or :open :open URL */
1108 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1109 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1110 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(client.gui.webview)))
1111 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1113 gtk_widget_grab_focus(inputbox);
1114 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1116 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1117 client.state.mode = ModeHints;
1118 a.i = Silent;
1119 switch (arg->s[0]) {
1120 case '.':
1121 a.s = g_strdup("hints.createHints('', 'f');");
1122 break;
1124 case ',':
1125 a.s = g_strdup("hints.createHints('', 'F');");
1126 break;
1128 case ';':
1129 a.s = NULL;
1130 if (arg->s[1]) {
1131 switch (arg->s[1]) {
1132 case 's':
1133 a.s = g_strdup("hints.createHints('', 's');");
1134 break;
1135 case 'y':
1136 a.s = g_strdup("hints.createHints('', 'y');");
1137 break;
1138 case 'o':
1139 a.s = g_strdup("hints.createHints('', 'f');");
1140 break;
1141 case 't': case 'w':
1142 a.s = g_strdup("hints.createHints('', 'F');");
1143 break;
1144 case 'O': case 'T': case 'W':
1145 a.s = g_strdup("hints.createHints('', 'O');");
1146 break;
1147 case 'i':
1148 a.s = g_strdup("hints.createHints('', 'i');");
1149 break;
1150 case 'I':
1151 a.s = g_strdup("hints.createHints('', 'I');");
1152 break;
1155 break;
1157 client.state.count = 0;
1158 if (a.s) {
1159 script(&a);
1160 g_free(a.s);
1164 return TRUE;
1167 gboolean
1168 navigate(const Arg *arg) {
1169 if (arg->i & NavigationForwardBack)
1170 webkit_web_view_go_back_or_forward(client.gui.webview, (arg->i == NavigationBack ? -1 : 1) * (client.state.count ? client.state.count : 1));
1171 else if (arg->i & NavigationReloadActions)
1172 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(client.gui.webview);
1173 else
1174 webkit_web_view_stop_loading(client.gui.webview);
1175 return TRUE;
1178 gboolean
1179 number(const Arg *arg) {
1180 const char *source = webkit_web_view_get_uri(client.gui.webview);
1181 char *uri, *p, *new;
1182 int number, diff = (client.state.count ? client.state.count : 1) * (arg->i == Increment ? 1 : -1);
1184 if (!source)
1185 return TRUE;
1186 uri = g_strdup(source); /* copy string */
1187 p =& uri[0];
1188 while(*p != '\0') /* goto the end of the string */
1189 ++p;
1190 --p;
1191 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1192 --p;
1193 if (*(++p) == '\0') { /* if no numbers were found abort */
1194 free(uri);
1195 return TRUE;
1197 number = atoi(p) + diff; /* apply diff on number */
1198 *p = '\0';
1199 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1200 webkit_web_view_load_uri(client.gui.webview, new);
1201 g_free(new);
1202 free(uri);
1203 return TRUE;
1206 gboolean
1207 open_arg(const Arg *arg) {
1208 char *argv[64];
1209 char *s = arg->s, *p = NULL, *new;
1210 Arg a = { .i = NavigationReload };
1211 int len;
1212 char *search_uri, *search_term;
1213 struct stat statbuf;
1215 if (client.state.embed) {
1216 gchar winid[64];
1217 snprintf(winid, LENGTH(winid), "%u", (gint)client.state.embed);
1218 argv[0] = *args;
1219 argv[1] = "-e";
1220 argv[2] = winid;
1221 argv[3] = arg->s;
1222 argv[4] = NULL;
1223 } else {
1224 argv[0] = *args;
1225 argv[1] = arg->s;
1226 argv[2] = NULL;
1229 if (!arg->s)
1230 navigate(&a);
1231 else if (arg->i == TargetCurrent) {
1232 while(*s == ' ') /* strip leading whitespace */
1233 ++s;
1234 p = (s + strlen(s) - 1);
1235 while(*p == ' ') /* strip trailing whitespace */
1236 --p;
1237 *(p + 1) = '\0';
1238 len = strlen(s);
1239 new = NULL;
1240 /* check for external handlers */
1241 if (open_handler(s))
1242 return TRUE;
1243 /* check for search engines */
1244 p = strchr(s, ' ');
1245 if (p) { /* check for search engines */
1246 *p = '\0';
1247 search_uri = find_uri_for_searchengine(s);
1248 if (search_uri != NULL) {
1249 search_term = soup_uri_encode(p+1, "&");
1250 new = g_strdup_printf(search_uri, search_term);
1251 g_free(search_term);
1253 *p = ' ';
1255 if (!new) {
1256 if (len > 3 && strstr(s, "://")) { /* valid url? */
1257 p = new = g_malloc(len + 1);
1258 while(*s != '\0') { /* strip whitespaces */
1259 if (*s != ' ')
1260 *(p++) = *s;
1261 ++s;
1263 *p = '\0';
1264 } else if (!stat(s, &statbuf)) { /* prepend "file://" */
1265 char *rpath = realpath(s, NULL);
1266 if (rpath != NULL) {
1267 len = strlen(rpath);
1268 new = g_malloc(sizeof("file://") + len);
1269 sprintf(new, "file://%s", rpath);
1270 free(rpath);
1271 } else {
1272 new = g_malloc(sizeof("file://") + len);
1273 sprintf(new, "file://%s", s);
1275 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1276 search_uri = find_uri_for_searchengine(defaultsearch);
1277 if (search_uri != NULL) {
1278 search_term = soup_uri_encode(s, "&");
1279 new = g_strdup_printf(search_uri, search_term);
1280 g_free(search_term);
1282 } else { /* prepend "http://" */
1283 new = g_malloc(sizeof("http://") + len);
1284 strcpy(new, "http://");
1285 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1288 webkit_web_view_load_uri(client.gui.webview, new);
1289 g_free(new);
1290 } else
1291 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1292 return TRUE;
1295 gboolean
1296 open_remembered(const Arg *arg)
1298 Arg a = {arg->i, client.state.rememberedURI};
1300 if (strcmp(client.state.rememberedURI, "")) {
1301 open_arg(&a);
1303 return TRUE;
1306 gboolean
1307 yank(const Arg *arg) {
1308 const char *url, *content;
1310 if (arg->i & SourceSelection) {
1311 webkit_web_view_copy_clipboard(client.gui.webview);
1312 if (arg->i & ClipboardPrimary)
1313 content = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1314 if (!content && arg->i & ClipboardGTK)
1315 content = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1316 if (content) {
1317 echo_message(Info, "Yanked %s", content);
1318 g_free((gpointer *)content);
1320 } else {
1321 if (arg->i & SourceURL) {
1322 url = webkit_web_view_get_uri(client.gui.webview);
1323 } else {
1324 url = arg->s;
1326 if (!url)
1327 return TRUE;
1329 echo_message(Info, "Yanked %s", url);
1330 if (arg->i & ClipboardPrimary)
1331 gtk_clipboard_set_text(client.state.clipboards[0], url, -1);
1332 if (arg->i & ClipboardGTK)
1333 gtk_clipboard_set_text(client.state.clipboards[1], url, -1);
1335 return TRUE;
1338 gboolean
1339 paste(const Arg *arg) {
1340 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1342 /* If we're over a link, open it in a new target. */
1343 if (strlen(client.state.rememberedURI) > 0) {
1344 Arg new_target = { .i = TargetNew, .s = arg->s };
1345 open_arg(&new_target);
1346 return TRUE;
1349 if (arg->i & ClipboardPrimary)
1350 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1351 if (!a.s && arg->i & ClipboardGTK)
1352 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1353 if (a.s) {
1354 open_arg(&a);
1355 g_free(a.s);
1357 return TRUE;
1360 gboolean
1361 quit(const Arg *arg) {
1362 FILE *f;
1363 const char *filename;
1364 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1365 if (uri != NULL) {
1366 /* write last URL into status file for recreation with "u" */
1367 filename = g_strdup_printf("%s", client.config.config_base);
1368 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1369 f = fopen(filename, "w");
1370 g_free((gpointer *)filename);
1371 if (f != NULL) {
1372 fprintf(f, "%s", uri);
1373 fclose(f);
1376 gtk_main_quit();
1377 return TRUE;
1380 gboolean
1381 revive(const Arg *arg) {
1382 FILE *f;
1383 const char *filename;
1384 char buffer[512] = "";
1385 Arg a = { .i = TargetNew, .s = NULL };
1386 /* get the URL of the window which has been closed last */
1387 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1388 f = fopen(filename, "r");
1389 g_free((gpointer *)filename);
1390 if (f != NULL) {
1391 fgets(buffer, 512, f);
1392 fclose(f);
1394 if (strlen(buffer) > 0) {
1395 a.s = buffer;
1396 open_arg(&a);
1397 return TRUE;
1399 return FALSE;
1402 static
1403 gboolean print_frame(const Arg *arg)
1405 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1406 webkit_web_frame_print (frame);
1407 return TRUE;
1410 gboolean
1411 search(const Arg *arg) {
1412 State *state = &client.state;
1413 state->count = state->count ? state->count : 1;
1414 gboolean success, direction = arg->i & DirectionPrev;
1416 if (arg->s) {
1417 if (state->search_handle) {
1418 g_free(state->search_handle);
1420 state->search_handle = g_strdup(arg->s);
1422 if (!state->search_handle)
1423 return TRUE;
1424 if (arg->i & DirectionAbsolute)
1425 state->search_direction = direction;
1426 else
1427 direction ^= state->search_direction;
1428 do {
1429 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, FALSE);
1430 if (!success) {
1431 if (arg->i & Wrapping) {
1432 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, TRUE);
1433 if (success) {
1434 echo_message(Warning, "search hit %s, continuing at %s",
1435 direction ? "BOTTOM" : "TOP",
1436 direction ? "TOP" : "BOTTOM");
1437 } else
1438 break;
1439 } else
1440 break;
1442 } while(--state->count);
1443 if (!success) {
1444 echo_message(Error, "Pattern not found: %s", state->search_handle);
1446 return TRUE;
1449 gboolean
1450 set(const Arg *arg) {
1451 switch (arg->i) {
1452 case ModeNormal:
1453 if (client.state.search_handle) {
1454 g_free(client.state.search_handle);
1455 client.state.search_handle = NULL;
1456 webkit_web_view_unmark_text_matches(client.gui.webview);
1458 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), "");
1459 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1460 break;
1461 case ModePassThrough:
1462 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1463 break;
1464 case ModeSendKey:
1465 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1466 break;
1467 case ModeInsert: /* should not be called manually but automatically */
1468 echo_message(Info | NoAutoHide, "-- INSERT --");
1469 break;
1470 default:
1471 return TRUE;
1473 client.state.mode = arg->i;
1474 return TRUE;
1477 gchar*
1478 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1479 JSStringRef string_ref;
1480 gchar *string;
1481 size_t length;
1483 string_ref = JSValueToStringCopy(context, ref, NULL);
1484 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1485 string = g_new(gchar, length);
1486 JSStringGetUTF8CString(string_ref, string, length);
1487 JSStringRelease(string_ref);
1488 return string;
1491 void
1492 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1493 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1494 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1495 JSStringRef str;
1496 JSValueRef val, exception;
1498 str = JSStringCreateWithUTF8CString(script);
1499 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1500 JSStringRelease(str);
1501 if (!val)
1502 *message = jsapi_ref_to_string(context, exception);
1503 else
1504 *value = jsapi_ref_to_string(context, val);
1507 gboolean
1508 quickmark(const Arg *a) {
1509 int i, b;
1510 b = atoi(a->s);
1511 char *fn = g_strdup_printf(QUICKMARK_FILE);
1512 FILE *fp;
1513 fp = fopen(fn, "r");
1514 g_free(fn);
1515 fn = NULL;
1516 char buf[100];
1518 if (fp != NULL && b < 10) {
1519 for( i=0; i < b; ++i ) {
1520 if (feof(fp)) {
1521 break;
1523 fgets(buf, 100, fp);
1525 char *ptr = strrchr(buf, '\n');
1526 *ptr = '\0';
1527 if (strlen(buf)) {
1528 Arg x = { .s = buf };
1529 return open_arg(&x);
1530 } else {
1531 echo_message(Error, "Quickmark %d not defined", b);
1532 return false;
1534 } else { return false; }
1537 gboolean
1538 script(const Arg *arg) {
1539 gchar *value = NULL, *message = NULL;
1540 char text[BUF_SIZE] = "";
1541 Arg a;
1542 WebKitNetworkRequest *request;
1543 WebKitDownload *download;
1545 if (!arg->s) {
1546 set_error("Missing argument.");
1547 return FALSE;
1549 jsapi_evaluate_script(arg->s, &value, &message);
1550 if (message) {
1551 set_error(message);
1552 g_free(value);
1553 g_free(message);
1554 return FALSE;
1556 g_free(message);
1557 if (arg->i != Silent && value) {
1558 echo_message(arg->i, value);
1560 /* switch mode according to scripts return value */
1561 if (value) {
1562 if (strncmp(value, "done;", 5) == 0) {
1563 a.i = ModeNormal;
1564 set(&a);
1565 } else if (strncmp(value, "insert;", 7) == 0) {
1566 a.i = ModeInsert;
1567 set(&a);
1568 client.state.manual_focus = TRUE;
1569 } else if (strncmp(value, "save;", 5) == 0) {
1570 /* forced download */
1571 a.i = ModeNormal;
1572 set(&a);
1573 request = webkit_network_request_new((value + 5));
1574 download = webkit_download_new(request);
1575 webview_download_cb(client.gui.webview, download, (gpointer *)NULL);
1576 } else if (strncmp(value, "yank;", 5) == 0) {
1577 /* yank link URL to clipboard */
1578 a.i = ModeNormal;
1579 set(&a);
1580 a.i = ClipboardPrimary | ClipboardGTK;
1581 a.s = (value + 5);
1582 yank(&a);
1583 } else if (strncmp(value, "colon;", 6) == 0) {
1584 /* use link URL for colon command */
1585 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(client.gui.inputbox)), 1023);
1586 a.i = ModeNormal;
1587 set(&a);
1588 switch (text[1]) {
1589 case 'O':
1590 a.s = g_strconcat(":open ", (value + 6), NULL);
1591 break;
1592 case 'T': case 'W':
1593 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1594 break;
1596 if (a.s) {
1597 input(&a);
1598 g_free(a.s);
1600 } else if (strncmp(value, "open;", 5) == 0 || strncmp(value, "tabopen;", 8) == 0) {
1601 /* TODO: open element */
1602 a.i = ModeNormal;
1603 set(&a);
1604 if (strncmp(value, "open;", 5) == 0)
1605 a.i = TargetCurrent;
1606 else
1607 a.i = TargetNew;
1608 a.s = (strchr(value, ';') + 1);
1609 open_arg(&a);
1610 } else if (strncmp(value, "error;", 6) == 0) {
1611 a.i = Error;
1612 set(&a);
1615 g_free(value);
1616 return TRUE;
1619 gboolean
1620 scroll(const Arg *arg) {
1621 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? client.gui.adjust_h : client.gui.adjust_v;
1622 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1623 float val = gtk_adjustment_get_value(adjust) / max * 100;
1624 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1625 unsigned int count = client.state.count;
1627 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1628 if (arg->i & ScrollMove)
1629 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1630 direction * /* direction */
1631 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1632 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1633 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1634 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1635 else
1636 gtk_adjustment_set_value(adjust,
1637 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1638 update_state();
1640 return TRUE;
1643 gboolean
1644 zoom(const Arg *arg) {
1645 unsigned int count = client.state.count;
1647 webkit_web_view_set_full_content_zoom(client.gui.webview, (arg->i & ZoomFullContent) > 0);
1648 webkit_web_view_set_zoom_level(client.gui.webview, (arg->i & ZoomOut) ?
1649 webkit_web_view_get_zoom_level(client.gui.webview) +
1650 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * client.config.zoomstep) :
1651 (count ? (float)count / 100.0 : 1.0));
1652 return TRUE;
1655 gboolean
1656 fake_key_event(const Arg *a) {
1657 if(!client.state.embed) {
1658 return FALSE;
1660 Display *xdpy;
1661 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1662 echo_message(Error, "Couldn't find the XDisplay.");
1663 return FALSE;
1666 XKeyEvent xk;
1667 xk.display = xdpy;
1668 xk.subwindow = None;
1669 xk.time = CurrentTime;
1670 xk.same_screen = True;
1671 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1672 xk.window = client.state.embed;
1673 xk.state = a->i;
1675 if( ! a->s ) {
1676 echo_message(Error, "Zero pointer as argument! Check your config.h");
1677 return FALSE;
1680 KeySym keysym;
1681 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1682 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1683 return FALSE;
1686 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1687 echo_message(Error, "Couldn't translate keysym to keycode");
1688 return FALSE;
1691 xk.type = KeyPress;
1692 if( !XSendEvent(xdpy, client.state.embed, True, KeyPressMask, (XEvent *)&xk) ) {
1693 echo_message(Error, "XSendEvent failed");
1694 return FALSE;
1696 XFlush(xdpy);
1698 return TRUE;
1701 gboolean
1702 commandhistoryfetch(const Arg *arg) {
1703 State *state = &client.state;
1704 const int length = g_list_length(client.state.commandhistory);
1705 gchar *input_message = NULL;
1707 if (length > 0) {
1708 if (arg->i == DirectionPrev) {
1709 state->commandpointer = (length + state->commandpointer - 1) % length;
1710 } else {
1711 state->commandpointer = (length + state->commandpointer + 1) % length;
1714 const char* command = (char *)g_list_nth_data(state->commandhistory, state->commandpointer);
1715 input_message = g_strconcat(":", command, NULL);
1716 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), input_message);
1717 g_free(input_message);
1718 gtk_editable_set_position(GTK_EDITABLE(client.gui.inputbox), -1);
1719 return TRUE;
1722 return FALSE;
1725 gboolean
1726 bookmark(const Arg *arg) {
1727 FILE *f;
1728 const char *filename;
1729 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1730 const char *title = webkit_web_view_get_title(client.gui.webview);
1731 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1732 f = fopen(filename, "a");
1733 g_free((gpointer *)filename);
1734 if (uri == NULL || strlen(uri) == 0) {
1735 set_error("No URI found to bookmark.");
1736 return FALSE;
1738 if (f != NULL) {
1739 fprintf(f, "%s", uri);
1740 if (title != NULL) {
1741 fprintf(f, "%s", " ");
1742 fprintf(f, "%s", title);
1744 if (arg->s && strlen(arg->s)) {
1745 build_taglist(arg, f);
1747 fprintf(f, "%s", "\n");
1748 fclose(f);
1749 echo_message(Info, "Bookmark saved");
1750 return TRUE;
1751 } else {
1752 set_error("Bookmarks file not found.");
1753 return FALSE;
1757 gboolean
1758 history() {
1759 FILE *f;
1760 const char *filename;
1761 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1762 const char *title = webkit_web_view_get_title(client.gui.webview);
1763 char *entry, buffer[512], *new;
1764 int n, i = 0;
1765 gboolean finished = FALSE;
1766 if (uri != NULL) {
1767 if (title != NULL) {
1768 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1769 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1770 } else {
1771 entry = malloc((strlen(uri) + 1) * sizeof(char));
1772 memset(entry, 0, strlen(uri) + 1);
1774 if (entry != NULL) {
1775 strncpy(entry, uri, strlen(uri));
1776 if (title != NULL) {
1777 strncat(entry, " ", 1);
1778 strncat(entry, title, strlen(title));
1780 n = strlen(entry);
1781 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1782 f = fopen(filename, "r");
1783 if (f != NULL) {
1784 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1785 if (new != NULL) {
1786 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1787 /* newest entries go on top */
1788 strncpy(new, entry, strlen(entry));
1789 strncat(new, "\n", 1);
1790 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1791 while (finished != TRUE) {
1792 if ((char *)NULL == fgets(buffer, 512, f)) {
1793 /* check if end of file was reached / error occured */
1794 if (!feof(f)) {
1795 break;
1797 /* end of file reached */
1798 finished = TRUE;
1799 continue;
1801 /* compare line (-1 because of newline character) */
1802 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1803 /* if the URI is already in history; we put it on top and skip it here */
1804 strncat(new, buffer, 512);
1805 i++;
1807 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1808 break;
1811 fclose(f);
1813 f = fopen(filename, "w");
1814 g_free((gpointer *)filename);
1815 if (f != NULL) {
1816 fprintf(f, "%s", new);
1817 fclose(f);
1819 if (new != NULL) {
1820 free(new);
1821 new = NULL;
1825 if (entry != NULL) {
1826 free(entry);
1827 entry = NULL;
1830 return TRUE;
1833 static gboolean
1834 view_source(const Arg * arg) {
1835 gboolean current_mode = webkit_web_view_get_view_source_mode(client.gui.webview);
1836 webkit_web_view_set_view_source_mode(client.gui.webview, !current_mode);
1837 webkit_web_view_reload(client.gui.webview);
1838 return TRUE;
1841 /* open an external editor defined by the protocol handler for
1842 vimprobableedit on a text box or similar */
1843 static gboolean
1844 open_editor(const Arg *arg) {
1845 char *text = NULL;
1846 gboolean success;
1847 GPid child_pid;
1848 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1849 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1850 temp_dir);
1851 int temp_file_handle = -1;
1853 /* check if active element is suitable for text editing */
1854 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1855 if (value == NULL) {
1856 g_free(message);
1857 return FALSE;
1859 tag = g_strdup(value);
1860 if (strcmp(tag, "INPUT") == 0) {
1861 /* extra check: type == text */
1862 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1863 if (strcmp(value, "text") != 0) {
1864 g_free(value);
1865 g_free(message);
1866 return FALSE;
1868 g_free(value);
1869 g_free(message);
1870 } else if (strcmp(tag, "TEXTAREA") != 0) {
1871 g_free(value);
1872 g_free(message);
1873 return FALSE;
1875 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1876 text = g_strdup(value);
1877 if (text == NULL) {
1878 g_free(value);
1879 g_free(message);
1880 return FALSE;
1883 /* write text into temporary file */
1884 temp_file_handle = mkstemp(temp_file_name);
1885 if (temp_file_handle == -1) {
1886 message = g_strdup_printf("Could not create temporary file: %s",
1887 strerror(errno));
1888 echo_message(Error, message);
1889 g_free(value);
1890 g_free(message);
1891 g_free(text);
1892 return FALSE;
1894 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1895 message = g_strdup_printf("Short write to temporary file: %s",
1896 strerror(errno));
1897 echo_message(Error, message);
1898 g_free(value);
1899 g_free(message);
1900 g_free(text);
1901 return FALSE;
1903 close(temp_file_handle);
1904 g_free(text);
1906 /* spawn editor */
1907 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1908 success = open_handler_pid(edit_url, &child_pid);
1909 g_free(edit_url);
1910 if (!success) {
1911 echo_message(Error, "External editor open failed (no handler for"
1912 " vimprobableedit protocol?)");
1913 unlink(temp_file_name);
1914 g_free(value);
1915 g_free(message);
1916 return FALSE;
1919 /* mark the active text box as "under processing" */
1920 jsapi_evaluate_script(
1921 "document.activeElement.disabled = true;"
1922 "document.activeElement.originalBackground = "
1923 " document.activeElement.style.background;"
1924 "document.activeElement.style.background = '#aaaaaa';"
1925 ,&value, &message);
1927 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1929 /* temp_file_name is freed in _resume_from_editor */
1930 g_free(value);
1931 g_free(message);
1932 g_free(tag);
1933 return TRUE;
1937 /* pick up from where open_editor left the work to the glib event loop.
1939 This is called when the external editor exits.
1941 The data argument points to allocated memory containing the temporary file
1942 name. */
1943 void
1944 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1945 FILE *fp;
1946 GString *set_value_js = g_string_new(
1947 "document.activeElement.value = \"");
1948 g_spawn_close_pid(child_pid);
1949 gchar *value = NULL, *message = NULL;
1950 gchar *temp_file_name = data;
1951 gchar buffer[BUF_SIZE] = "";
1952 gchar *buf_ptr = buffer;
1953 int char_read;
1955 jsapi_evaluate_script(
1956 "document.activeElement.disabled = true;"
1957 "document.activeElement.style.background = '#aaaaaa';"
1958 ,&value, &message);
1959 g_free(value);
1960 g_free(message);
1962 if (child_status) {
1963 echo_message(Error, "External editor returned with non-zero status,"
1964 " discarding edits.");
1965 goto error_exit;
1968 /* re-read the new contents of the file and put it into the HTML element */
1969 if (!access(temp_file_name, R_OK) == 0) {
1970 message = g_strdup_printf("Could not access temporary file: %s",
1971 strerror(errno));
1972 goto error_exit;
1974 fp = fopen(temp_file_name, "r");
1975 if (fp == NULL) {
1976 /* this would be too weird to even emit an error message */
1977 goto error_exit;
1979 jsapi_evaluate_script("document.activeElement.value = '';",
1980 &value, &message);
1981 g_free(value);
1982 g_free(message);
1984 while (EOF != (char_read = fgetc(fp))) {
1985 if (char_read == '\n') {
1986 *buf_ptr++ = '\\';
1987 *buf_ptr++ = 'n';
1988 } else if (char_read == '"') {
1989 *buf_ptr++ = '\\';
1990 *buf_ptr++ = '"';
1991 } else {
1992 *buf_ptr++ = char_read;
1994 /* ship out as the buffer when space gets tight. This has
1995 fuzz to save on thinking, plus we have enough space for the
1996 trailing "; in any case. */
1997 if (buf_ptr-buffer>=BUF_SIZE-10) {
1998 *buf_ptr = 0;
1999 g_string_append(set_value_js, buffer);
2000 buf_ptr = buffer;
2003 *buf_ptr++ = '"';
2004 *buf_ptr++ = ';';
2005 *buf_ptr = 0;
2006 g_string_append(set_value_js, buffer);
2007 fclose(fp);
2009 jsapi_evaluate_script(set_value_js->str, &value, &message);
2011 /* Fall through, error and normal exit are identical */
2012 error_exit:
2013 jsapi_evaluate_script(
2014 "document.activeElement.disabled = false;"
2015 "document.activeElement.style.background ="
2016 " document.activeElement.originalBackground;"
2017 ,&value, &message);
2019 g_string_free(set_value_js, TRUE);
2020 unlink(temp_file_name);
2021 g_free(temp_file_name);
2022 g_free(value);
2023 g_free(message);
2026 static gboolean
2027 focus_input(const Arg *arg) {
2028 static Arg a;
2030 a.s = g_strdup("hints.focusInput();");
2031 a.i = Silent;
2032 script(&a);
2033 g_free(a.s);
2034 update_state();
2035 client.state.manual_focus = TRUE;
2036 return TRUE;
2039 static void
2040 clear_focus(void) {
2041 static Arg a;
2043 a.s = g_strdup("hints.clearFocus();");
2044 a.i = Silent;
2045 script(&a);
2046 g_free(a.s);
2047 a.i = ModeNormal;
2048 a.s = NULL;
2049 set(&a);
2052 static gboolean
2053 browser_settings(const Arg *arg) {
2054 char line[255];
2055 if (!arg->s) {
2056 set_error("Missing argument.");
2057 return FALSE;
2059 strncpy(line, arg->s, 254);
2060 if (process_set_line(line))
2061 return TRUE;
2062 else {
2063 set_error("Invalid setting.");
2064 return FALSE;
2068 char *
2069 search_word(int whichword) {
2070 int k = 0;
2071 static char word[240];
2072 char *c = my_pair.line;
2074 while (isspace(*c) && *c)
2075 c++;
2077 switch (whichword) {
2078 case 0:
2079 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2080 word[k++] = *c;
2081 c++;
2083 word[k] = '\0';
2084 strncpy(my_pair.what, word, 20);
2085 break;
2086 case 1:
2087 while (*c && k < 240) {
2088 word[k++] = *c;
2089 c++;
2091 word[k] = '\0';
2092 strncpy(my_pair.value, word, 240);
2093 break;
2096 return c;
2099 static gboolean
2100 process_set_line(char *line) {
2101 char *c;
2102 int listlen, i;
2103 gboolean boolval;
2104 WebKitWebSettings *settings;
2106 settings = webkit_web_view_get_settings(client.gui.webview);
2107 my_pair.line = line;
2108 c = search_word(0);
2109 if (!strlen(my_pair.what))
2110 return FALSE;
2112 while (isspace(*c) && *c)
2113 c++;
2115 if (*c == ':' || *c == '=')
2116 c++;
2118 my_pair.line = c;
2119 c = search_word(1);
2121 listlen = LENGTH(browsersettings);
2122 for (i = 0; i < listlen; i++) {
2123 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2124 /* mandatory argument not provided */
2125 if (strlen(my_pair.value) == 0)
2126 return FALSE;
2127 /* process qmark? */
2128 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2129 return (process_save_qmark(my_pair.value, client.gui.webview));
2131 /* interpret boolean values */
2132 if (browsersettings[i].boolval) {
2133 if (strncmp(my_pair.value, "on", 2) == 0 || strncmp(my_pair.value, "true", 4) == 0 || strncmp(my_pair.value, "ON", 2) == 0 || strncmp(my_pair.value, "TRUE", 4) == 0) {
2134 boolval = TRUE;
2135 } else if (strncmp(my_pair.value, "off", 3) == 0 || strncmp(my_pair.value, "false", 5) == 0 || strncmp(my_pair.value, "OFF", 3) == 0 || strncmp(my_pair.value, "FALSE", 5) == 0) {
2136 boolval = FALSE;
2137 } else {
2138 return FALSE;
2140 } else if (browsersettings[i].colourval) {
2141 /* interpret as hexadecimal colour */
2142 if (!parse_colour(my_pair.value)) {
2143 return FALSE;
2146 if (browsersettings[i].var != NULL) {
2147 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2148 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2149 /* in this case, \0 will not have been copied */
2150 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2151 /* in case this string is also used for a webkit setting, make sure it's consistent */
2152 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2153 echo_message(Info, "String too long; automatically truncated!");
2156 if (strlen(browsersettings[i].webkit) > 0) {
2157 /* activate appropriate webkit setting */
2158 if (browsersettings[i].boolval) {
2159 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2160 } else if (browsersettings[i].intval) {
2161 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2162 } else {
2163 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2165 webkit_web_view_set_settings(client.gui.webview, settings);
2168 if (strlen(my_pair.what) == 14) {
2169 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2170 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2171 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2172 complete_case_sensitive = boolval;
2174 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2175 toggle_proxy(boolval);
2176 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2177 toggle_scrollbars(boolval);
2178 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2179 gtk_widget_set_visible(GTK_WIDGET(client.gui.statusbar), boolval);
2180 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2181 gtk_widget_set_visible(client.gui.inputbox, boolval);
2182 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2183 escape_input_on_load = boolval;
2186 /* SSL certificate checking */
2187 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2188 if (boolval) {
2189 strict_ssl = TRUE;
2190 g_object_set(G_OBJECT(client.net.session), "ssl-strict", TRUE, NULL);
2191 } else {
2192 strict_ssl = FALSE;
2193 g_object_set(G_OBJECT(client.net.session), "ssl-strict", FALSE, NULL);
2196 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2197 g_object_set(G_OBJECT(client.net.session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2199 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2200 set_default_winsize(my_pair.value);
2203 /* reload page? */
2204 if (browsersettings[i].reload)
2205 webkit_web_view_reload(client.gui.webview);
2206 return TRUE;
2209 return FALSE;
2212 gboolean
2213 process_line(char *line) {
2214 char *c = line, *command_hist;
2215 int i;
2216 size_t len, length = strlen(line);
2217 gboolean found = FALSE, success = FALSE;
2218 Arg a;
2219 GList *l;
2221 while (isspace(*c))
2222 c++;
2223 /* Ignore blank lines. */
2224 if (c[0] == '\0')
2225 return TRUE;
2227 command_hist = g_strdup(c);
2229 /* check for colon command aliases first */
2230 for (l = client.config.colon_aliases; l; l = g_list_next(l)) {
2231 Alias *alias = (Alias *)l->data;
2232 if (length == strlen(alias->alias) && strncmp(alias->alias, line, length) == 0) {
2233 /* reroute to target command */
2234 c = alias->target;
2235 length = strlen(alias->target);
2236 break;
2240 /* check standard commands */
2241 for (i = 0; i < LENGTH(commands); i++) {
2242 if (commands[i].cmd == NULL)
2243 break;
2244 len = strlen(commands[i].cmd);
2245 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2246 found = TRUE;
2247 a.i = commands[i].arg.i;
2248 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2249 success = commands[i].func(&a);
2250 g_free(a.s);
2251 break;
2255 save_command_history(command_hist);
2256 g_free(command_hist);
2258 if (!found) {
2259 echo_message(Error, "Not a browser command: %s", c);
2260 } else if (!success) {
2261 if (client.state.error_msg != NULL) {
2262 echo_message(Error, client.state.error_msg);
2263 g_free(client.state.error_msg);
2264 client.state.error_msg = NULL;
2265 } else {
2266 echo_message(Error, "Unknown error. Please file a bug report!");
2269 return success;
2272 static gboolean
2273 search_tag(const Arg * a) {
2274 FILE *f;
2275 const char *filename;
2276 const char *tag = a->s;
2277 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2278 int t, i, intag, k;
2280 if (!tag) {
2281 /* The user must give us something to load up. */
2282 set_error("Bookmark tag required with this option.");
2283 return FALSE;
2286 if (strlen(tag) > MAXTAGSIZE) {
2287 set_error("Tag too long.");
2288 return FALSE;
2291 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2292 f = fopen(filename, "r");
2293 g_free((gpointer *)filename);
2294 if (f == NULL) {
2295 set_error("Couldn't open bookmarks file.");
2296 return FALSE;
2298 while (fgets(s, BUFFERSIZE-1, f)) {
2299 intag = 0;
2300 t = strlen(s) - 1;
2301 while (isspace(s[t]))
2302 t--;
2303 if (s[t] != ']') continue;
2304 while (t > 0) {
2305 if (s[t] == ']') {
2306 if (!intag)
2307 intag = t;
2308 else
2309 intag = 0;
2310 } else {
2311 if (s[t] == '[') {
2312 if (intag) {
2313 i = 0;
2314 k = t + 1;
2315 while (k < intag)
2316 foundtag[i++] = s[k++];
2317 foundtag[i] = '\0';
2318 /* foundtag now contains the tag */
2319 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2320 i = 0;
2321 while (isspace(s[i])) i++;
2322 k = 0;
2323 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2324 url[k] = '\0';
2325 Arg x = { .i = TargetNew, .s = url };
2326 open_arg(&x);
2329 intag = 0;
2332 t--;
2335 return TRUE;
2338 void
2339 toggle_proxy(gboolean onoff) {
2340 SoupURI *proxy_uri;
2341 char *filename, *new;
2343 if (onoff == FALSE) {
2344 g_object_set(client.net.session, "proxy-uri", NULL, NULL);
2345 } else {
2346 filename = (char *)g_getenv("http_proxy");
2348 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2349 * defined.
2351 if (filename == NULL)
2352 filename = (char *)g_getenv("HTTP_PROXY");
2354 if (filename != NULL && 0 < strlen(filename)) {
2355 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2356 proxy_uri = soup_uri_new(new);
2358 g_object_set(client.net.session, "proxy-uri", proxy_uri, NULL);
2360 soup_uri_free(proxy_uri);
2361 g_free(new);
2366 void
2367 toggle_scrollbars(gboolean onoff) {
2368 Gui *gui = &client.gui;
2369 if (onoff == TRUE) {
2370 gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2371 gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2372 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2373 } else {
2374 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2375 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2376 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2378 gtk_widget_set_scroll_adjustments (GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2380 return;
2383 void set_default_winsize(const char * const size) {
2384 char *p;
2385 int x = 640, y = 480;
2387 x = strtol(size, &p, 10);
2388 if (errno == ERANGE || x <= 0) {
2389 x = 640;
2390 goto out;
2393 if (p == size || strlen(size) == p - size)
2394 goto out;
2396 y = strtol(p + 1, NULL, 10);
2397 if (errno == ERANGE || y <= 0)
2398 y = 480;
2400 out:
2401 gtk_window_resize(GTK_WINDOW(client.gui.window), x, y);
2404 void
2405 update_url(const char *uri) {
2406 Gui *gui = &client.gui;
2407 gboolean ssl = g_str_has_prefix(uri, "https://");
2408 GdkColor color;
2409 WebKitWebFrame *frame;
2410 WebKitWebDataSource *src;
2411 WebKitNetworkRequest *request;
2412 SoupMessage *msg;
2413 gboolean ssl_ok;
2414 char *sslactivecolor;
2415 gchar *markup;
2416 #ifdef ENABLE_HISTORY_INDICATOR
2417 char before[] = " [";
2418 char after[] = "]";
2419 gboolean back = webkit_web_view_can_go_back(gui->webview);
2420 gboolean fwd = webkit_web_view_can_go_forward(gui->webview);
2422 if (!back && !fwd)
2423 before[0] = after[0] = '\0';
2424 #endif
2425 markup = g_markup_printf_escaped(
2426 #ifdef ENABLE_HISTORY_INDICATOR
2427 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2428 before, back ? "+" : "", fwd ? "-" : "", after
2429 #else
2430 "<span font=\"%s\">%s</span>", statusfont, uri
2431 #endif
2433 gtk_label_set_markup(GTK_LABEL(gui->status_url), markup);
2434 g_free(markup);
2435 if (ssl) {
2436 frame = webkit_web_view_get_main_frame(gui->webview);
2437 src = webkit_web_frame_get_data_source(frame);
2438 request = webkit_web_data_source_get_request(src);
2439 msg = webkit_network_request_get_message(request);
2440 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2441 if (ssl_ok)
2442 sslactivecolor = sslbgcolor;
2443 else
2444 sslactivecolor = sslinvalidbgcolor;
2446 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2447 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &color);
2448 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2449 gtk_widget_modify_fg(GTK_WIDGET(gui->status_url), GTK_STATE_NORMAL, &color);
2450 gtk_widget_modify_fg(GTK_WIDGET(gui->status_state), GTK_STATE_NORMAL, &color);
2453 void
2454 update_state() {
2455 State* state = &client.state;
2456 char *markup;
2457 int download_count = g_list_length(state->activeDownloads);
2458 GString *status = g_string_new("");
2460 /* construct the status line */
2462 /* count, modkey and input buffer */
2463 g_string_append_printf(status, "%.0d", state->count);
2464 if (state->current_modkey) g_string_append_c(status, state->current_modkey);
2466 /* the number of active downloads */
2467 if (state->activeDownloads) {
2468 g_string_append_printf(status, " %d active %s", download_count,
2469 (download_count == 1) ? "download" : "downloads");
2472 #ifdef ENABLE_WGET_PROGRESS_BAR
2473 /* the progressbar */
2475 int progress = -1;
2476 char progressbar[progressbartick + 1];
2478 if (state->activeDownloads) {
2479 progress = 0;
2480 GList *ptr;
2482 for (ptr = state->activeDownloads; ptr; ptr = g_list_next(ptr)) {
2483 progress += 100 * webkit_download_get_progress(ptr->data);
2486 progress /= download_count;
2488 } else if (webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FINISHED
2489 && webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FAILED) {
2491 progress = webkit_web_view_get_progress(client.gui.webview) * 100;
2494 if (progress >= 0) {
2495 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2496 g_string_append_printf(status, " %c%s%c",
2497 progressborderleft, progressbar, progressborderright);
2500 #endif
2502 /* and the current scroll position */
2504 int max = gtk_adjustment_get_upper(client.gui.adjust_v) - gtk_adjustment_get_page_size(client.gui.adjust_v);
2505 int val = (int)(gtk_adjustment_get_value(client.gui.adjust_v) / max * 100);
2507 if (max == 0)
2508 g_string_append(status, " All");
2509 else if (val == 0)
2510 g_string_append(status, " Top");
2511 else if (val == 100)
2512 g_string_append(status, " Bot");
2513 else
2514 g_string_append_printf(status, " %d%%", val);
2518 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2519 gtk_label_set_markup(GTK_LABEL(client.gui.status_state), markup);
2520 g_free(markup);
2522 g_string_free(status, TRUE);
2525 static void
2526 setup_client(void) {
2527 State *state = &client.state;
2528 Config *config = &client.config;
2530 state->mode = ModeNormal;
2531 state->count = 0;
2532 state->rememberedURI[0] = '\0';
2533 state->manual_focus = FALSE;
2534 state->is_inspecting = FALSE;
2535 state->commandhistory = NULL;
2536 state->commandpointer = 0;
2538 config->colon_aliases = NULL;
2539 config->cookie_timeout = 4800;
2542 void
2543 setup_modkeys() {
2544 unsigned int i;
2545 client.config.modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2546 char *ptr = client.config.modkeys;
2548 for (i = 0; i < LENGTH(keys); i++)
2549 if (keys[i].modkey && !strchr(client.config.modkeys, keys[i].modkey))
2550 *(ptr++) = keys[i].modkey;
2551 client.config.modkeys = realloc(client.config.modkeys, &ptr[0] - &client.config.modkeys[0] + 1);
2554 void
2555 setup_gui() {
2556 Gui *gui = &client.gui;
2557 State *state = &client.state;
2559 gui->scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2560 gui->scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2561 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2562 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2563 if (client.state.embed) {
2564 gui->window = GTK_WINDOW(gtk_plug_new(client.state.embed));
2565 } else {
2566 gui->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2567 gtk_window_set_wmclass(GTK_WINDOW(gui->window), "vimprobable2", "Vimprobable2");
2569 gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
2570 gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2571 gui->inputbox = gtk_entry_new();
2572 gui->webview = (WebKitWebView*)webkit_web_view_new();
2573 gui->statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2574 gui->eventbox = gtk_event_box_new();
2575 gui->status_url = gtk_label_new(NULL);
2576 gui->status_state = gtk_label_new(NULL);
2577 GdkColor bg;
2578 PangoFontDescription *font;
2579 GdkGeometry hints = { 1, 1 };
2580 gui->inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gui->webview));
2582 state->clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2583 state->clipboards[1] = gtk_clipboard_get(GDK_NONE);
2584 setup_settings();
2585 gdk_color_parse(statusbgcolor, &bg);
2586 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
2587 gtk_widget_set_name(GTK_WIDGET(gui->window), "Vimprobable2");
2588 gtk_window_set_geometry_hints(gui->window, NULL, &hints, GDK_HINT_MIN_SIZE);
2590 state->keymap = gdk_keymap_get_default();
2592 #ifdef DISABLE_SCROLLBAR
2593 gui->viewport = gtk_scrolled_window_new(NULL, NULL);
2594 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2595 #else
2596 /* Ensure we still see scrollbars. */
2597 gui->viewport = gtk_scrolled_window_new(gui->adjust_h, gui->adjust_v);
2598 #endif
2600 gui->pane = gtk_vpaned_new();
2601 gtk_paned_pack1(GTK_PANED(gui->pane), GTK_WIDGET(gui->box), TRUE, TRUE);
2603 setup_signals();
2604 gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
2606 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2607 * titlebars, we can still scroll.
2609 gtk_widget_set_scroll_adjustments(GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2611 font = pango_font_description_from_string(urlboxfont[0]);
2612 gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
2613 pango_font_description_free(font);
2614 gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
2615 gtk_misc_set_alignment(GTK_MISC(gui->status_url), 0.0, 0.0);
2616 gtk_misc_set_alignment(GTK_MISC(gui->status_state), 1.0, 0.0);
2617 gtk_box_pack_start(gui->statusbar, gui->status_url, TRUE, TRUE, 2);
2618 gtk_box_pack_start(gui->statusbar, gui->status_state, FALSE, FALSE, 2);
2619 gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar));
2620 gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
2621 gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
2622 gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
2623 gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
2624 gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane));
2625 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
2626 gtk_widget_show_all(GTK_WIDGET(gui->window));
2627 set_widget_font_and_color(gui->inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2628 g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2631 void
2632 setup_settings() {
2633 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2634 char *filename, *file_url;
2636 client.net.session = webkit_get_default_session();
2637 g_object_set(G_OBJECT(client.net.session), "ssl-ca-file", ca_bundle, NULL);
2638 g_object_set(G_OBJECT(client.net.session), "ssl-strict", strict_ssl, NULL);
2639 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2640 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2641 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2642 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2643 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2644 filename = g_strdup_printf(USER_STYLESHEET);
2645 file_url = g_strdup_printf("file://%s", filename);
2646 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2647 g_free(file_url);
2648 g_free(filename);
2649 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2650 g_object_get(G_OBJECT(settings), "zoom-step", &client.config.zoomstep, NULL);
2651 webkit_web_view_set_settings(client.gui.webview, settings);
2653 /* proxy */
2654 toggle_proxy(use_proxy);
2657 void
2658 setup_signals() {
2659 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
2660 #ifdef ENABLE_COOKIE_SUPPORT
2661 /* Headers. */
2662 g_signal_connect_after(G_OBJECT(client.net.session), "request-started", G_CALLBACK(new_generic_request), NULL);
2663 #endif
2664 /* Accept-language header */
2665 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2666 /* window */
2667 g_object_connect(G_OBJECT(client.gui.window),
2668 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2669 NULL);
2670 /* frame */
2671 g_signal_connect(G_OBJECT(frame),
2672 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2673 /* webview */
2674 g_object_connect(G_OBJECT(client.gui.webview),
2675 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2676 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2677 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2678 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2679 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2680 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2681 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2682 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2683 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2684 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2685 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2686 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2687 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2688 NULL);
2689 /* webview adjustment */
2690 g_object_connect(G_OBJECT(client.gui.adjust_v),
2691 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2692 NULL);
2693 /* inputbox */
2694 g_object_connect(G_OBJECT(client.gui.inputbox),
2695 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2696 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2697 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2698 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2699 NULL);
2700 /* inspector */
2701 g_signal_connect(G_OBJECT(client.gui.inspector),
2702 "inspect-web-view", G_CALLBACK(inspector_new_cb), NULL);
2703 g_signal_connect(G_OBJECT(client.gui.inspector),
2704 "show-window", G_CALLBACK(inspector_show_cb), NULL);
2705 g_signal_connect(G_OBJECT(client.gui.inspector),
2706 "close-window", G_CALLBACK(inspector_close_cb), NULL);
2707 g_signal_connect(G_OBJECT(client.gui.inspector),
2708 "finished", G_CALLBACK(inspector_finished_cb), NULL);
2711 #ifdef ENABLE_USER_SCRIPTFILE
2712 static void
2713 scripts_run_user_file() {
2714 gchar *js = NULL, *user_scriptfile = NULL;
2715 GError *error = NULL;
2717 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2719 /* run the users script file */
2720 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2721 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2723 gchar *value = NULL, *message = NULL;
2725 jsapi_evaluate_script(js, &value, &message);
2726 g_free(js);
2727 if (message) {
2728 fprintf(stderr, "%s", message);
2730 g_free(value);
2731 g_free(message);
2732 } else {
2733 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2736 g_free(user_scriptfile);
2738 #endif
2740 #ifdef ENABLE_COOKIE_SUPPORT
2741 void
2742 setup_cookies()
2744 Network *net = &client.net;
2745 if (net->file_cookie_jar)
2746 g_object_unref(net->file_cookie_jar);
2748 if (net->session_cookie_jar)
2749 g_object_unref(net->session_cookie_jar);
2751 net->session_cookie_jar = soup_cookie_jar_new();
2753 net->cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2755 load_all_cookies();
2757 g_signal_connect(G_OBJECT(net->file_cookie_jar), "changed",
2758 G_CALLBACK(update_cookie_jar), NULL);
2761 /* TA: XXX - we should be using this callback for any header-requests we
2762 * receive (hence the name "new_generic_request" -- but for now, its use
2763 * is limited to handling cookies.
2765 void
2766 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2768 SoupMessageHeaders *soup_msg_h;
2769 SoupURI *uri;
2770 char *cookie_str;
2772 soup_msg_h = soup_msg->request_headers;
2773 soup_message_headers_remove(soup_msg_h, "Cookie");
2774 uri = soup_message_get_uri(soup_msg);
2775 if ((cookie_str = get_cookies(uri))) {
2776 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2777 g_free(cookie_str);
2780 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2782 return;
2785 char *
2786 get_cookies(SoupURI *soup_uri) {
2787 char *cookie_str;
2789 cookie_str = soup_cookie_jar_get_cookies(client.net.file_cookie_jar, soup_uri, TRUE);
2791 return cookie_str;
2794 void
2795 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2797 GSList *resp_cookie = NULL, *cookie_list;
2798 SoupCookie *cookie;
2800 cookie_list = soup_cookies_from_response(soup_msg);
2801 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2803 SoupDate *soup_date;
2804 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2806 if (client.config.cookie_timeout && cookie->expires == NULL) {
2807 soup_date = soup_date_new_from_time_t(time(NULL) + client.config.cookie_timeout * 10);
2808 soup_cookie_set_expires(cookie, soup_date);
2809 soup_date_free(soup_date);
2811 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
2814 soup_cookies_free(cookie_list);
2816 return;
2819 void
2820 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2822 if (!new) {
2823 /* Nothing to do. */
2824 return;
2827 SoupCookie *copy;
2828 copy = soup_cookie_copy(new);
2830 soup_cookie_jar_add_cookie(client.net.session_cookie_jar, copy);
2832 return;
2835 void
2836 load_all_cookies(void)
2838 Network *net = &client.net;
2839 GSList *cookie_list;
2840 net->file_cookie_jar = soup_cookie_jar_text_new(net->cookie_store, COOKIES_STORAGE_READONLY);
2842 /* Put them back in the session store. */
2843 GSList *cookies_from_file = soup_cookie_jar_all_cookies(net->file_cookie_jar);
2844 cookie_list = cookies_from_file;
2846 for (; cookies_from_file;
2847 cookies_from_file = cookies_from_file->next)
2849 soup_cookie_jar_add_cookie(net->session_cookie_jar, cookies_from_file->data);
2852 soup_cookies_free(cookies_from_file);
2853 g_slist_free(cookie_list);
2855 return;
2858 #endif
2860 void
2861 mop_up(void) {
2862 /* Free up any nasty globals before exiting. */
2863 #ifdef ENABLE_COOKIE_SUPPORT
2864 if (client.net.cookie_store)
2865 g_free(client.net.cookie_store);
2866 #endif
2867 return;
2871 main(int argc, char *argv[]) {
2872 static Arg a;
2873 static char url[256] = "";
2874 static gboolean ver = false;
2875 static gboolean configfile_exists = FALSE;
2876 static const char *cfile = NULL;
2877 static gchar *winid = NULL;
2878 static GOptionEntry opts[] = {
2879 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2880 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2881 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2882 { NULL }
2884 static GError *err;
2885 args = argv;
2886 Config *config = &client.config;
2888 /* command line argument: version */
2889 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2890 g_printerr("can't init gtk: %s\n", err->message);
2891 g_error_free(err);
2892 return EXIT_FAILURE;
2895 if (ver) {
2896 printf("%s\n", INTERNAL_VERSION);
2897 return EXIT_SUCCESS;
2900 setup_client();
2902 if (getenv("TMPDIR")) {
2903 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2904 temp_dir[MAX_SETTING_SIZE-1] = 0;
2907 if( getenv("XDG_CONFIG_HOME") )
2908 config->config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2909 else
2910 config->config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2912 if (cfile)
2913 config->configfile = g_strdup(cfile);
2914 else
2915 config->configfile = g_strdup_printf(RCFILE);
2917 if (!g_thread_supported())
2918 g_thread_init(NULL);
2920 if (winid) {
2921 if (strncmp(winid, "0x", 2) == 0) {
2922 client.state.embed = strtol(winid, NULL, 16);
2923 } else {
2924 client.state.embed = atoi(winid);
2928 setup_modkeys();
2929 make_keyslist();
2930 setup_gui();
2931 #ifdef ENABLE_COOKIE_SUPPORT
2932 setup_cookies();
2933 #endif
2935 make_searchengines_list(searchengines, LENGTH(searchengines));
2936 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2938 /* Check if the specified file exists. */
2939 /* And only warn the user, if they explicitly asked for a config on the
2940 * command line.
2942 if (!(access(config->configfile, F_OK) == 0) && cfile) {
2943 echo_message(Info, "Config file '%s' doesn't exist", cfile);
2944 } else if ((access(config->configfile, F_OK) == 0))
2945 configfile_exists = true;
2947 /* read config file */
2948 /* But only report errors if we failed, and the file existed. */
2949 if ((SUCCESS != read_rcfile(config->configfile)) && configfile_exists) {
2950 echo_message(Error, "Error in config file '%s'", config->configfile);
2951 g_free(config->configfile);
2954 /* command line argument: URL */
2955 if (argc > 1) {
2956 strncpy(url, argv[argc - 1], 255);
2957 } else {
2958 strncpy(url, startpage, 255);
2961 a.i = TargetCurrent;
2962 a.s = url;
2963 open_arg(&a);
2964 gtk_main();
2966 mop_up();
2968 return EXIT_SUCCESS;