Add support for ";l" to show a link destination in the status bar.
[vimprobable.git] / main.c
blobef485c1e90ea48591c0d89e337783974ac39926b
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);
108 static void show_link(const char *link);
110 static gboolean history(void);
111 static gboolean process_set_line(char *line);
112 void save_command_history(char *line);
113 void toggle_proxy(gboolean onoff);
114 void toggle_scrollbars(gboolean onoff);
115 void set_default_winsize(const char * const size);
117 gboolean process_keypress(GdkEventKey *event);
118 void fill_suggline(char * suggline, const char * command, const char *fill_with);
119 GtkWidget * fill_eventbox(const char * completion_line);
120 static void mop_up(void);
122 #include "main.h"
124 /* variables */
125 static char **args;
127 #include "config.h"
128 #include "keymap.h"
130 /* Cookie support. */
131 #ifdef ENABLE_COOKIE_SUPPORT
132 static void setup_cookies(void);
133 static char *get_cookies(SoupURI *soup_uri);
134 static void load_all_cookies(void);
135 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
136 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
137 static void handle_response_headers(SoupMessage *soup_msg, gpointer unused);
138 #endif
140 Client client;
142 /* callbacks */
143 void
144 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
145 quit(NULL);
148 void
149 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
150 gtk_window_set_title(client.gui.window, title);
153 void
154 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
155 #ifdef ENABLE_GTK_PROGRESS_BAR
156 gtk_entry_set_progress_fraction(GTK_ENTRY(client.gui.inputbox), progress == 100 ? 0 : (double)progress/100);
157 #endif
158 update_state();
161 #ifdef ENABLE_WGET_PROGRESS_BAR
162 void
163 ascii_bar(int total, int state, char *string) {
164 int i;
166 for (i = 0; i < state; i++)
167 string[i] = progressbartickchar;
168 string[i++] = progressbarcurrent;
169 for (; i < total; i++)
170 string[i] = progressbarspacer;
171 string[i] = '\0';
173 #endif
175 void
176 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
177 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
178 const char *uri = webkit_web_view_get_uri(webview);
180 update_url(uri);
181 script(&a);
182 g_free(a.s);
183 scripts_run_user_file();
185 if (client.state.mode == ModeInsert || client.state.mode == ModeHints) {
186 Arg a = { .i = ModeNormal };
187 set(&a);
189 client.state.manual_focus = FALSE;
192 void
193 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
194 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
195 gboolean scripts;
197 g_object_get(settings, "enable-scripts", &scripts, NULL);
198 if (escape_input_on_load && scripts && !client.state.manual_focus && !gtk_widget_is_focus(client.gui.inputbox)) {
199 clear_focus();
201 if (HISTORY_MAX_ENTRIES > 0)
202 history();
203 update_state();
206 void
207 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
208 /* retrieve the URI of the temporary webview */
209 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
210 /* clean up */
211 webkit_web_view_stop_loading(temp_view);
212 gtk_widget_destroy(GTK_WIDGET(temp_view));
213 /* open the requested window */
214 open_arg(&a);
217 static WebKitWebView *
218 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
219 /* create a temporary webview to execute the script in */
220 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
221 /* wait until the new webview receives its new URI */
222 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
223 return temp_view;
226 gboolean
227 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
228 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
229 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
230 open_arg(&a);
231 webkit_web_policy_decision_ignore(decision);
232 return TRUE;
235 gboolean
236 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
237 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
238 SoupMessage *msg = webkit_network_request_get_message(request);
239 guint http_status = msg->status_code;
241 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
242 if (SOUP_STATUS_IS_SUCCESSFUL(http_status)) {
243 webkit_web_policy_decision_download(decision);
244 return TRUE;
246 return FALSE;
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 static void
463 show_link(const char *link) {
464 char *markup;
466 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
467 gtk_label_set_markup(GTK_LABEL(client.gui.status_url), markup);
468 strncpy(client.state.rememberedURI, link, BUF_SIZE);
469 g_free(markup);
472 void
473 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
474 const char *uri = webkit_web_view_get_uri(webview);
476 memset(client.state.rememberedURI, 0, BUF_SIZE);
477 if (link)
478 show_link(link);
479 else
480 update_url(uri);
483 gboolean
484 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
485 Arg a;
487 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
488 if (gtk_window_has_toplevel_focus(client.gui.window)) {
489 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
490 a.i = ModeNormal;
491 return set(&a);
492 } else if (!strcmp(message, "insertmode_on")) {
493 a.i = ModeInsert;
494 return set(&a);
497 return FALSE;
500 void
501 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
502 Gui *gui = &client.gui;
503 State *state = &client.state;
504 char *text;
505 guint16 length = gtk_entry_get_text_length(entry);
506 Arg a;
507 gboolean forward = FALSE;
509 a.i = HideCompletion;
510 complete(&a);
511 if (length == 0)
512 return;
513 text = (char*)gtk_entry_get_text(entry);
515 /* move focus from inputbox to print potential messages that could not be
516 * printed as long as the inputbox is focused */
517 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
519 if (length > 1 && text[0] == ':') {
520 process_line((text + 1));
521 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
522 webkit_web_view_unmark_text_matches(gui->webview);
523 #ifdef ENABLE_MATCH_HIGHLITING
524 webkit_web_view_mark_text_matches(gui->webview, &text[1], FALSE, 0);
525 webkit_web_view_set_highlight_text_matches(gui->webview, TRUE);
526 #endif
527 state->count = 0;
528 #ifndef ENABLE_INCREMENTAL_SEARCH
529 a.s =& text[1];
530 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
531 search(&a);
532 #else
533 state->search_direction = forward;
534 if (state->search_handle) {
535 g_free(state->search_handle);
537 state->search_handle = g_strdup(&text[1]);
538 #endif
539 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
540 a.i = Silent;
541 a.s = g_strdup_printf("hints.fire();");
542 script(&a);
543 g_free(a.s);
544 update_state();
545 } else
546 return;
547 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
550 gboolean
551 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
552 Arg a;
553 int numval;
554 State *state = &client.state;
556 if (state->mode == ModeHints) {
557 if (event->keyval == GDK_Tab) {
558 a.i = Silent;
559 a.s = g_strdup_printf("hints.focusNextHint();");
560 script(&a);
561 g_free(a.s);
562 update_state();
563 return TRUE;
565 if (event->keyval == GDK_ISO_Left_Tab) {
566 a.i = Silent;
567 a.s = g_strdup_printf("hints.focusPreviousHint();");
568 script(&a);
569 g_free(a.s);
570 update_state();
571 return TRUE;
573 if (event->keyval == GDK_Return) {
574 a.i = Silent;
575 a.s = g_strdup_printf("hints.fire();");
576 script(&a);
577 g_free(a.s);
578 update_state();
579 return TRUE;
582 switch (event->keyval) {
583 case GDK_bracketleft:
584 case GDK_Escape:
585 if (!IS_ESCAPE(event)) break;
586 a.i = HideCompletion;
587 complete(&a);
588 a.i = ModeNormal;
589 state->commandpointer = 0;
590 return set(&a);
591 break;
592 case GDK_Tab:
593 a.i = DirectionNext;
594 return complete(&a);
595 break;
596 case GDK_Up:
597 a.i = DirectionPrev;
598 return commandhistoryfetch(&a);
599 break;
600 case GDK_Down:
601 a.i = DirectionNext;
602 return commandhistoryfetch(&a);
603 break;
604 case GDK_ISO_Left_Tab:
605 a.i = DirectionPrev;
606 return complete(&a);
607 break;
610 if (state->mode == ModeHints) {
611 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
612 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
613 (event->keyval == GDK_BackSpace)) {
614 state->count /= 10;
615 a.i = Silent;
616 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
617 script(&a);
618 g_free(a.s);
619 update_state();
620 return TRUE;
623 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
624 if ((numval >= 1 && numval <= 9) || (numval == 0 && state->count)) {
625 /* allow a zero as non-first number */
626 state->count = (state->count ? state->count * 10 : 0) + numval;
627 a.i = Silent;
628 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
629 script(&a);
630 g_free(a.s);
631 update_state();
632 return TRUE;
636 return FALSE;
639 gboolean
640 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
641 int i;
642 WebKitHitTestResult *result;
643 WebKitHitTestResultContext context;
644 State *state = &client.state;
645 if (state->mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
646 /* handle mouse click events */
647 for (i = 0; i < LENGTH(mouse); i++) {
648 if (mouse[i].mask == CLEAN(event->button.state)
649 && (mouse[i].modkey == state->current_modkey
650 || (!mouse[i].modkey && !state->current_modkey)
651 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
652 && mouse[i].button == event->button.button
653 && mouse[i].func) {
654 if (mouse[i].func(&mouse[i].arg)) {
655 state->current_modkey = state->count = 0;
656 update_state();
657 return TRUE;
661 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
662 g_object_get(result, "context", &context, NULL);
663 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
664 Arg a = { .i = ModeInsert };
665 set(&a);
666 state->manual_focus = TRUE;
668 } else if (state->mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
669 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
670 g_object_get(result, "context", &context, NULL);
671 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
672 Arg a = { .i = ModeNormal };
673 set(&a);
675 } else {
676 gchar *value = NULL, *message = NULL;
677 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
678 if (value && !strcmp(value, "[object HTMLFormElement]")) {
679 Arg a = { .i = ModeInsert, .s = NULL };
680 set(&a);
681 state->manual_focus = TRUE;
683 g_free(value);
684 g_free(message);
686 return FALSE;
689 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
690 Arg a;
691 guint16 length = gtk_entry_get_text_length(entry);
693 if (!length) {
694 a.i = HideCompletion;
695 complete(&a);
696 a.i = ModeNormal;
697 return set(&a);
699 return FALSE;
702 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
703 Arg a;
704 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
705 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
706 gboolean forward = FALSE;
708 /* Update incremental search if the user changes the search text.
710 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
711 * from the user. But if the entry is focused and the text is set
712 * through gtk_entry_set_text() in some asyncrounous operation,
713 * I would consider that a bug.
716 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
717 webkit_web_view_unmark_text_matches(client.gui.webview);
718 webkit_web_view_search_text(client.gui.webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
719 return TRUE;
720 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
721 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
722 a.i = Silent;
723 switch (text[0]) {
724 case '.':
725 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
726 break;
728 case ',':
729 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
730 break;
732 case ';':
733 a.s = NULL;
734 switch (text[1]) {
735 case 's':
736 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
737 break;
738 case 'y':
739 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
740 break;
741 case 'o':
742 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
743 break;
744 case 't': case 'w':
745 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
746 break;
747 case 'O': case 'T': case 'W':
748 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
749 break;
750 case 'i':
751 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
752 break;
753 case 'I':
754 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
755 break;
756 case 'l':
757 a.s = g_strconcat("hints.createHints('", text + 2, "', 'l');", NULL);
758 break;
760 break;
762 client.state.count = 0;
763 if (a.s) {
764 script(&a);
765 g_free(a.s);
768 return TRUE;
769 } else if (length == 0) {
770 client.state.mode = ModeNormal;
771 a.i = Silent;
772 a.s = g_strdup("hints.clearHints();");
773 script(&a);
774 g_free(a.s);
775 client.state.count = 0;
776 update_state();
779 return FALSE;
782 /* funcs here */
784 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
785 memset(suggline, 0, 512);
786 strncpy(suggline, command, 512);
787 strncat(suggline, " ", 1);
788 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
791 GtkWidget * fill_eventbox(const char * completion_line) {
792 GtkBox * row;
793 GtkWidget *row_eventbox, *el;
794 GdkColor color;
795 char *markup, *markup_tmp;
797 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
798 row_eventbox = gtk_event_box_new();
799 gdk_color_parse(completionbgcolor[0], &color);
800 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
801 el = gtk_label_new(NULL);
802 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
803 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
804 markup_tmp, "</span>", NULL);
805 gtk_label_set_markup(GTK_LABEL(el), markup);
806 g_free(markup_tmp);
807 g_free(markup);
808 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
809 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
810 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
811 return row_eventbox;
814 gboolean
815 complete(const Arg *arg) {
816 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
817 size_t listlen, len, cmdlen;
818 int i, spacepos;
819 Listelement *elementlist = NULL, *elementpointer;
820 gboolean highlight = FALSE;
821 GtkBox *row;
822 GtkWidget *row_eventbox, *el;
823 GtkBox *_table;
824 GdkColor color;
825 static GtkWidget *table, *top_border;
826 static char *prefix;
827 static char **suggestions;
828 static GtkWidget **widgets;
829 static int n = 0, m, current = -1;
830 Gui *gui = &client.gui;
832 str = (char*)gtk_entry_get_text(GTK_ENTRY(gui->inputbox));
833 len = strlen(str);
835 /* Get the length of the list of commands for completion. We need this to
836 * malloc/realloc correctly.
838 listlen = LENGTH(commands);
840 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
841 return TRUE;
842 if (prefix) {
843 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
844 gdk_color_parse(completionbgcolor[0], &color);
845 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
846 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
847 if ((arg->i == DirectionNext && current == 0)
848 || (arg->i == DirectionPrev && current == n - 1))
849 current = -1;
850 } else {
851 free(widgets);
852 free(suggestions);
853 free(prefix);
854 gtk_widget_destroy(GTK_WIDGET(table));
855 gtk_widget_destroy(GTK_WIDGET(top_border));
856 table = NULL;
857 widgets = NULL;
858 suggestions = NULL;
859 prefix = NULL;
860 n = 0;
861 current = -1;
862 if (arg->i == HideCompletion)
863 return TRUE;
865 } else if (arg->i == HideCompletion)
866 return TRUE;
867 if (!widgets) {
868 prefix = g_strdup(str);
869 widgets = malloc(sizeof(GtkWidget*) * listlen);
870 suggestions = malloc(sizeof(char*) * listlen);
871 top_border = gtk_event_box_new();
872 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
873 gdk_color_parse(completioncolor[2], &color);
874 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
875 table = gtk_event_box_new();
876 gdk_color_parse(completionbgcolor[0], &color);
877 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
878 highlight = len > 1;
879 if (strchr(str, ' ') == NULL) {
880 /* command completion */
881 listlen = LENGTH(commands);
882 for (i = 0; i < listlen; i++) {
883 if (commands[i].cmd == NULL)
884 break;
885 cmdlen = strlen(commands[i].cmd);
886 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
887 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
888 if (highlight) {
889 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
890 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
891 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
892 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
894 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
895 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
896 row_eventbox = gtk_event_box_new();
897 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
898 el = gtk_label_new(NULL);
899 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
900 free(s);
901 gtk_label_set_markup(GTK_LABEL(el), markup);
902 g_free(markup);
903 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
904 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
905 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
906 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
907 suggestions[n] = commands[i].cmd;
908 widgets[n++] = row_eventbox;
911 } else {
912 entry = (char *)malloc(512 * sizeof(char));
913 if (entry == NULL) {
914 return FALSE;
916 memset(entry, 0, 512);
917 suggurls = malloc(sizeof(char*) * listlen);
918 if (suggurls == NULL) {
919 return FALSE;
921 spacepos = strcspn(str, " ");
922 searchfor = (str + spacepos + 1);
923 strncpy(command, (str + 1), spacepos - 1);
924 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
925 /* browser settings */
926 listlen = LENGTH(browsersettings);
927 for (i = 0; i < listlen; i++) {
928 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
929 /* match */
930 fill_suggline(suggline, command, browsersettings[i].name);
931 /* FIXME(HP): This memory is never freed */
932 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
933 strncpy(suggurls[n], suggline, 512);
934 suggestions[n] = suggurls[n];
935 row_eventbox = fill_eventbox(suggline);
936 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
937 widgets[n++] = row_eventbox;
941 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
942 /* completion on tags */
943 spacepos = strcspn(str, " ");
944 searchfor = (str + spacepos + 1);
945 elementlist = complete_list(searchfor, 1, elementlist);
946 } else {
947 /* URL completion: bookmarks */
948 elementlist = complete_list(searchfor, 0, elementlist);
949 m = count_list(elementlist);
950 if (m < MAX_LIST_SIZE) {
951 /* URL completion: history */
952 elementlist = complete_list(searchfor, 2, elementlist);
955 elementpointer = elementlist;
956 while (elementpointer != NULL) {
957 fill_suggline(suggline, command, elementpointer->element);
958 /* FIXME(HP): This memory is never freed */
959 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
960 strncpy(suggurls[n], suggline, 512);
961 suggestions[n] = suggurls[n];
962 row_eventbox = fill_eventbox(suggline);
963 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
964 widgets[n++] = row_eventbox;
965 elementpointer = elementpointer->next;
966 if (n >= MAX_LIST_SIZE)
967 break;
969 free_list(elementlist);
970 if (suggurls != NULL) {
971 free(suggurls);
972 suggurls = NULL;
974 if (entry != NULL) {
975 free(entry);
976 entry = NULL;
979 /* TA: FIXME - this needs rethinking entirely. */
981 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
982 if (widgets_temp == NULL && widgets == NULL) {
983 fprintf(stderr, "Couldn't realloc() widgets\n");
984 exit(1);
986 widgets = widgets_temp;
987 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
988 if (suggestions_temp == NULL && suggestions == NULL) {
989 fprintf(stderr, "Couldn't realloc() suggestions\n");
990 exit(1);
992 suggestions = suggestions_temp;
994 if (!n) {
995 gdk_color_parse(completionbgcolor[1], &color);
996 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
997 el = gtk_label_new(NULL);
998 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
999 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
1000 gtk_label_set_markup(GTK_LABEL(el), markup);
1001 g_free(markup);
1002 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
1004 gtk_box_pack_start(gui->box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
1005 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
1006 gtk_box_pack_start(gui->box, GTK_WIDGET(table), FALSE, FALSE, 0);
1007 gtk_widget_show_all(GTK_WIDGET(table));
1008 gtk_widget_show_all(GTK_WIDGET(top_border));
1009 if (!n)
1010 return TRUE;
1011 current = arg->i == DirectionPrev ? n - 1 : 0;
1013 if (current != -1) {
1014 gdk_color_parse(completionbgcolor[2], &color);
1015 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1016 s = g_strconcat(":", suggestions[current], NULL);
1017 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), s);
1018 g_free(s);
1019 } else
1020 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), prefix);
1021 gtk_editable_set_position(GTK_EDITABLE(gui->inputbox), -1);
1022 return TRUE;
1025 gboolean
1026 descend(const Arg *arg) {
1027 char *source = (char*)webkit_web_view_get_uri(client.gui.webview), *p = &source[0], *new;
1028 int i, len;
1029 client.state.count = client.state.count ? client.state.count : 1;
1031 if (!source)
1032 return TRUE;
1033 if (arg->i == Rootdir) {
1034 for (i = 0; i < 3; i++) /* get to the third slash */
1035 if (!(p = strchr(++p, '/')))
1036 return TRUE; /* if we cannot find it quit */
1037 } else {
1038 len = strlen(source);
1039 if (!len) /* if string is empty quit */
1040 return TRUE;
1041 p = source + len; /* start at the end */
1042 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1043 ++client.state.count;
1044 for (i = 0; i < client.state.count; i++)
1045 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1046 if (p == source) /* if we reach the first char pointer quit */
1047 return TRUE;
1048 ++p; /* since we do p-- in the while, we are pointing at
1049 the char before the slash, so +1 */
1051 len = p - source + 1; /* new length = end - start + 1 */
1052 new = malloc(len + 1);
1053 memcpy(new, source, len);
1054 new[len] = '\0';
1055 webkit_web_view_load_uri(client.gui.webview, new);
1056 free(new);
1057 return TRUE;
1060 gboolean
1061 echo(const Arg *arg) {
1062 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1064 if (index < Info || index > Error)
1065 return TRUE;
1067 if (!gtk_widget_is_focus(GTK_WIDGET(client.gui.inputbox))) {
1068 set_widget_font_and_color(client.gui.inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1069 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), !arg->s ? "" : arg->s);
1072 return TRUE;
1075 static gboolean
1076 open_inspector(const Arg * arg) {
1077 gboolean inspect_enabled;
1078 WebKitWebSettings *settings;
1079 State *state = &client.state;
1081 settings = webkit_web_view_get_settings(client.gui.webview);
1082 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1083 if (inspect_enabled) {
1084 if (state->is_inspecting) {
1085 webkit_web_inspector_close(client.gui.inspector);
1086 } else {
1087 webkit_web_inspector_show(client.gui.inspector);
1089 return TRUE;
1090 } else {
1091 echo_message(Error, "Webinspector is not enabled");
1092 return FALSE;
1096 gboolean
1097 input(const Arg *arg) {
1098 int pos = 0;
1099 client.state.count = 0;
1100 const char *url;
1101 int index = Info;
1102 Arg a;
1103 GtkWidget *inputbox = client.gui.inputbox;
1105 /* if inputbox hidden, show it again */
1106 if (!gtk_widget_get_visible(inputbox))
1107 gtk_widget_set_visible(inputbox, TRUE);
1109 update_state();
1111 /* Set the colour and font back to the default, so that we don't still
1112 * maintain a red colour from a warning from an end of search indicator,
1113 * etc.
1115 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1117 /* to avoid things like :open URL :open URL2 or :open :open URL */
1118 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1119 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1120 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(client.gui.webview)))
1121 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1123 gtk_widget_grab_focus(inputbox);
1124 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1126 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1127 client.state.mode = ModeHints;
1128 a.i = Silent;
1129 switch (arg->s[0]) {
1130 case '.':
1131 a.s = g_strdup("hints.createHints('', 'f');");
1132 break;
1134 case ',':
1135 a.s = g_strdup("hints.createHints('', 'F');");
1136 break;
1138 case ';':
1139 a.s = NULL;
1140 if (arg->s[1]) {
1141 switch (arg->s[1]) {
1142 case 's':
1143 a.s = g_strdup("hints.createHints('', 's');");
1144 break;
1145 case 'y':
1146 a.s = g_strdup("hints.createHints('', 'y');");
1147 break;
1148 case 'o':
1149 a.s = g_strdup("hints.createHints('', 'f');");
1150 break;
1151 case 't': case 'w':
1152 a.s = g_strdup("hints.createHints('', 'F');");
1153 break;
1154 case 'O': case 'T': case 'W':
1155 a.s = g_strdup("hints.createHints('', 'O');");
1156 break;
1157 case 'i':
1158 a.s = g_strdup("hints.createHints('', 'i');");
1159 break;
1160 case 'I':
1161 a.s = g_strdup("hints.createHints('', 'I');");
1162 break;
1163 case 'l':
1164 a.s = g_strdup("hints.createHints('', 'l');");
1165 break;
1168 break;
1170 client.state.count = 0;
1171 if (a.s) {
1172 script(&a);
1173 g_free(a.s);
1177 return TRUE;
1180 gboolean
1181 navigate(const Arg *arg) {
1182 if (arg->i & NavigationForwardBack)
1183 webkit_web_view_go_back_or_forward(client.gui.webview, (arg->i == NavigationBack ? -1 : 1) * (client.state.count ? client.state.count : 1));
1184 else if (arg->i & NavigationReloadActions)
1185 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(client.gui.webview);
1186 else
1187 webkit_web_view_stop_loading(client.gui.webview);
1188 return TRUE;
1191 gboolean
1192 number(const Arg *arg) {
1193 const char *source = webkit_web_view_get_uri(client.gui.webview);
1194 char *uri, *p, *new;
1195 int number, diff = (client.state.count ? client.state.count : 1) * (arg->i == Increment ? 1 : -1);
1197 if (!source)
1198 return TRUE;
1199 uri = g_strdup(source); /* copy string */
1200 p =& uri[0];
1201 while(*p != '\0') /* goto the end of the string */
1202 ++p;
1203 --p;
1204 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1205 --p;
1206 if (*(++p) == '\0') { /* if no numbers were found abort */
1207 free(uri);
1208 return TRUE;
1210 number = atoi(p) + diff; /* apply diff on number */
1211 *p = '\0';
1212 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1213 webkit_web_view_load_uri(client.gui.webview, new);
1214 g_free(new);
1215 free(uri);
1216 return TRUE;
1219 gboolean
1220 open_arg(const Arg *arg) {
1221 char *argv[64];
1222 char *s = arg->s, *p = NULL, *new;
1223 Arg a = { .i = NavigationReload };
1224 int len, space = 0;
1225 char *search_uri, *search_term;
1226 struct stat statbuf;
1228 if (client.state.embed) {
1229 gchar winid[64];
1230 snprintf(winid, LENGTH(winid), "%u", (gint)client.state.embed);
1231 argv[0] = *args;
1232 argv[1] = "-e";
1233 argv[2] = winid;
1234 argv[3] = arg->s;
1235 argv[4] = NULL;
1236 } else {
1237 argv[0] = *args;
1238 argv[1] = arg->s;
1239 argv[2] = NULL;
1242 if (!arg->s)
1243 navigate(&a);
1244 else if (arg->i == TargetCurrent) {
1245 while(*s == ' ') /* strip leading whitespace */
1246 ++s;
1247 p = (s + strlen(s) - 1);
1248 while(*p == ' ') /* strip trailing whitespace */
1249 --p;
1250 *(p + 1) = '\0';
1251 len = strlen(s);
1252 new = NULL;
1253 /* check for external handlers */
1254 if (open_handler(s))
1255 return TRUE;
1256 /* check for search engines */
1257 p = strchr(s, ' ');
1258 if (!p) {
1259 /* shortcut without search term */
1260 p = s;
1261 } else {
1262 /* search term given */
1263 *p = '\0';
1264 space = 1;
1266 search_uri = find_uri_for_searchengine(s);
1267 if (search_uri != NULL) {
1268 if (space > 0) {
1269 search_term = soup_uri_encode(p+1, "&");
1270 new = g_strdup_printf(search_uri, search_term);
1271 g_free(search_term);
1272 } else {
1273 if (!strstr(search_uri, "%s"))
1274 new = g_strdup_printf(search_uri);
1275 else {
1276 /* the search engine definition expected an argument */
1277 new = g_strdup_printf(search_uri, "");
1281 if (space > 0)
1282 *p = ' ';
1283 if (!new) {
1284 if (len > 3 && strstr(s, "://")) { /* valid url? */
1285 p = new = g_malloc(len + 1);
1286 while(*s != '\0') { /* strip whitespaces */
1287 if (*s != ' ')
1288 *(p++) = *s;
1289 ++s;
1291 *p = '\0';
1292 } else if (!stat(s, &statbuf)) { /* prepend "file://" */
1293 char *rpath = realpath(s, NULL);
1294 if (rpath != NULL) {
1295 len = strlen(rpath);
1296 new = g_malloc(sizeof("file://") + len);
1297 sprintf(new, "file://%s", rpath);
1298 free(rpath);
1299 } else {
1300 new = g_malloc(sizeof("file://") + len);
1301 sprintf(new, "file://%s", s);
1303 } else if (space > 0 || !strchr(s, '.')) { /* whitespaces or no dot? */
1304 search_uri = find_uri_for_searchengine(defaultsearch);
1305 if (search_uri != NULL) {
1306 search_term = soup_uri_encode(s, "&");
1307 new = g_strdup_printf(search_uri, search_term);
1308 g_free(search_term);
1310 } else { /* prepend "http://" */
1311 new = g_malloc(sizeof("http://") + len);
1312 strcpy(new, "http://");
1313 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1316 webkit_web_view_load_uri(client.gui.webview, new);
1317 g_free(new);
1318 } else
1319 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1320 return TRUE;
1323 gboolean
1324 open_remembered(const Arg *arg)
1326 Arg a = {arg->i, client.state.rememberedURI};
1328 if (strcmp(client.state.rememberedURI, "")) {
1329 open_arg(&a);
1331 return TRUE;
1334 gboolean
1335 yank(const Arg *arg) {
1336 const char *url, *content;
1338 if (arg->i & SourceSelection) {
1339 webkit_web_view_copy_clipboard(client.gui.webview);
1340 if (arg->i & ClipboardPrimary)
1341 content = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1342 if (!content && arg->i & ClipboardGTK)
1343 content = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1344 if (content) {
1345 echo_message(Info, "Yanked %s", content);
1346 g_free((gpointer *)content);
1348 } else {
1349 if (arg->i & SourceURL) {
1350 url = webkit_web_view_get_uri(client.gui.webview);
1351 } else {
1352 url = arg->s;
1354 if (!url)
1355 return TRUE;
1357 echo_message(Info, "Yanked %s", url);
1358 if (arg->i & ClipboardPrimary)
1359 gtk_clipboard_set_text(client.state.clipboards[0], url, -1);
1360 if (arg->i & ClipboardGTK)
1361 gtk_clipboard_set_text(client.state.clipboards[1], url, -1);
1363 return TRUE;
1366 gboolean
1367 paste(const Arg *arg) {
1368 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1370 /* If we're over a link, open it in a new target. */
1371 if (strlen(client.state.rememberedURI) > 0) {
1372 Arg new_target = { .i = TargetNew, .s = arg->s };
1373 open_arg(&new_target);
1374 return TRUE;
1377 if (arg->i & ClipboardPrimary)
1378 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1379 if (!a.s && arg->i & ClipboardGTK)
1380 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1381 if (a.s) {
1382 open_arg(&a);
1383 g_free(a.s);
1385 return TRUE;
1388 gboolean
1389 quit(const Arg *arg) {
1390 FILE *f;
1391 const char *filename;
1392 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1393 if (uri != NULL) {
1394 /* write last URL into status file for recreation with "u" */
1395 filename = g_strdup_printf("%s", client.config.config_base);
1396 filename = g_strdup_printf(private_mode ? "/dev/null" : CLOSED_URL_FILENAME);
1397 f = fopen(filename, "w");
1398 g_free((gpointer *)filename);
1399 if (f != NULL) {
1400 fprintf(f, "%s", uri);
1401 fclose(f);
1404 gtk_main_quit();
1405 return TRUE;
1408 gboolean
1409 revive(const Arg *arg) {
1410 FILE *f;
1411 const char *filename;
1412 char buffer[512] = "";
1413 Arg a = { .i = TargetNew, .s = NULL };
1414 /* get the URL of the window which has been closed last */
1415 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1416 f = fopen(filename, "r");
1417 g_free((gpointer *)filename);
1418 if (f != NULL) {
1419 fgets(buffer, 512, f);
1420 fclose(f);
1422 if (strlen(buffer) > 0) {
1423 a.s = buffer;
1424 open_arg(&a);
1425 return TRUE;
1427 return FALSE;
1430 static
1431 gboolean print_frame(const Arg *arg)
1433 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1434 webkit_web_frame_print (frame);
1435 return TRUE;
1438 gboolean
1439 search(const Arg *arg) {
1440 State *state = &client.state;
1441 state->count = state->count ? state->count : 1;
1442 gboolean success, direction = arg->i & DirectionPrev;
1444 if (arg->s) {
1445 if (state->search_handle) {
1446 g_free(state->search_handle);
1448 state->search_handle = g_strdup(arg->s);
1450 if (!state->search_handle)
1451 return TRUE;
1452 if (arg->i & DirectionAbsolute)
1453 state->search_direction = direction;
1454 else
1455 direction ^= state->search_direction;
1456 do {
1457 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, FALSE);
1458 if (!success) {
1459 if (arg->i & Wrapping) {
1460 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, TRUE);
1461 if (success) {
1462 echo_message(Warning, "search hit %s, continuing at %s",
1463 direction ? "BOTTOM" : "TOP",
1464 direction ? "TOP" : "BOTTOM");
1465 } else
1466 break;
1467 } else
1468 break;
1470 } while(--state->count);
1471 if (!success) {
1472 echo_message(Error, "Pattern not found: %s", state->search_handle);
1474 return TRUE;
1477 gboolean
1478 set(const Arg *arg) {
1479 switch (arg->i) {
1480 case ModeNormal:
1481 if (client.state.search_handle) {
1482 g_free(client.state.search_handle);
1483 client.state.search_handle = NULL;
1484 webkit_web_view_unmark_text_matches(client.gui.webview);
1486 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), "");
1487 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1488 break;
1489 case ModePassThrough:
1490 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1491 break;
1492 case ModeSendKey:
1493 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1494 break;
1495 case ModeInsert: /* should not be called manually but automatically */
1496 /* make sure we leaf focus from inputbox to show the new mode */
1497 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1498 echo_message(Info | NoAutoHide, "-- INSERT --");
1499 break;
1500 default:
1501 return TRUE;
1503 client.state.mode = arg->i;
1504 return TRUE;
1507 gchar*
1508 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1509 JSStringRef string_ref;
1510 gchar *string;
1511 size_t length;
1513 string_ref = JSValueToStringCopy(context, ref, NULL);
1514 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1515 string = g_new(gchar, length);
1516 JSStringGetUTF8CString(string_ref, string, length);
1517 JSStringRelease(string_ref);
1518 return string;
1521 void
1522 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1523 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1524 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1525 JSStringRef str;
1526 JSValueRef val, exception;
1528 str = JSStringCreateWithUTF8CString(script);
1529 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1530 JSStringRelease(str);
1531 if (!val)
1532 *message = jsapi_ref_to_string(context, exception);
1533 else
1534 *value = jsapi_ref_to_string(context, val);
1537 gboolean
1538 quickmark(const Arg *a) {
1539 int i, b;
1540 b = atoi(a->s);
1541 char *fn = g_strdup_printf(QUICKMARK_FILE);
1542 FILE *fp;
1543 fp = fopen(fn, "r");
1544 g_free(fn);
1545 fn = NULL;
1546 char buf[100];
1548 if (fp != NULL && b < 10) {
1549 for( i=0; i < b; ++i ) {
1550 if (feof(fp)) {
1551 break;
1553 fgets(buf, 100, fp);
1555 char *ptr = strrchr(buf, '\n');
1556 *ptr = '\0';
1557 if (strlen(buf)) {
1558 Arg x = { .s = buf };
1559 return open_arg(&x);
1560 } else {
1561 echo_message(Error, "Quickmark %d not defined", b);
1562 return false;
1564 } else { return false; }
1567 gboolean
1568 script(const Arg *arg) {
1569 gchar *value = NULL, *message = NULL;
1570 char text[BUF_SIZE] = "";
1571 Arg a;
1572 WebKitNetworkRequest *request;
1573 WebKitDownload *download;
1575 if (!arg->s) {
1576 set_error("Missing argument.");
1577 return FALSE;
1579 jsapi_evaluate_script(arg->s, &value, &message);
1580 if (message) {
1581 set_error(message);
1582 g_free(value);
1583 g_free(message);
1584 return FALSE;
1586 g_free(message);
1587 if (arg->i != Silent && value) {
1588 echo_message(arg->i, value);
1590 /* switch mode according to scripts return value */
1591 if (value) {
1592 if (strncmp(value, "done;", 5) == 0) {
1593 a.i = ModeNormal;
1594 set(&a);
1595 } else if (strncmp(value, "insert;", 7) == 0) {
1596 a.i = ModeInsert;
1597 set(&a);
1598 client.state.manual_focus = TRUE;
1599 } else if (strncmp(value, "save;", 5) == 0) {
1600 /* forced download */
1601 a.i = ModeNormal;
1602 set(&a);
1603 request = webkit_network_request_new((value + 5));
1604 download = webkit_download_new(request);
1605 webview_download_cb(client.gui.webview, download, (gpointer *)NULL);
1606 } else if (strncmp(value, "yank;", 5) == 0) {
1607 /* yank link URL to clipboard */
1608 a.i = ModeNormal;
1609 set(&a);
1610 a.i = ClipboardPrimary | ClipboardGTK;
1611 a.s = (value + 5);
1612 yank(&a);
1613 } else if (strncmp(value, "colon;", 6) == 0) {
1614 /* use link URL for colon command */
1615 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(client.gui.inputbox)), 1023);
1616 a.i = ModeNormal;
1617 set(&a);
1618 switch (text[1]) {
1619 case 'O':
1620 a.s = g_strconcat(":open ", (value + 6), NULL);
1621 break;
1622 case 'T': case 'W':
1623 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1624 break;
1626 if (a.s) {
1627 input(&a);
1628 g_free(a.s);
1630 } else if (strncmp(value, "open;", 5) == 0 || strncmp(value, "tabopen;", 8) == 0) {
1631 /* TODO: open element */
1632 a.i = ModeNormal;
1633 set(&a);
1634 if (strncmp(value, "open;", 5) == 0)
1635 a.i = TargetCurrent;
1636 else
1637 a.i = TargetNew;
1638 a.s = (strchr(value, ';') + 1);
1639 open_arg(&a);
1640 } else if (strncmp(value, "show_link;", 10) == 0) {
1641 a.i = ModeNormal;
1642 set(&a);
1643 char *link = strchr(value, ';') + 1;
1644 if (link) {
1645 memset(client.state.rememberedURI, 0, BUF_SIZE);
1646 show_link(link);
1648 } else if (strncmp(value, "error;", 6) == 0) {
1649 a.i = Error;
1650 set(&a);
1653 g_free(value);
1654 return TRUE;
1657 gboolean
1658 scroll(const Arg *arg) {
1659 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? client.gui.adjust_h : client.gui.adjust_v;
1660 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1661 float val = gtk_adjustment_get_value(adjust) / max * 100;
1662 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1663 unsigned int count = client.state.count;
1665 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1666 if (arg->i & ScrollMove)
1667 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1668 direction * /* direction */
1669 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1670 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1671 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1672 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1673 else
1674 gtk_adjustment_set_value(adjust,
1675 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1676 update_state();
1678 return TRUE;
1681 gboolean
1682 zoom(const Arg *arg) {
1683 unsigned int count = client.state.count;
1685 webkit_web_view_set_full_content_zoom(client.gui.webview, (arg->i & ZoomFullContent) > 0);
1686 webkit_web_view_set_zoom_level(client.gui.webview, (arg->i & ZoomOut) ?
1687 webkit_web_view_get_zoom_level(client.gui.webview) +
1688 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * client.config.zoomstep) :
1689 (count ? (float)count / 100.0 : 1.0));
1690 return TRUE;
1693 gboolean
1694 fake_key_event(const Arg *a) {
1695 if(!client.state.embed) {
1696 return FALSE;
1698 Display *xdpy;
1699 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1700 echo_message(Error, "Couldn't find the XDisplay.");
1701 return FALSE;
1704 XKeyEvent xk;
1705 xk.display = xdpy;
1706 xk.subwindow = None;
1707 xk.time = CurrentTime;
1708 xk.same_screen = True;
1709 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1710 xk.window = client.state.embed;
1711 xk.state = a->i;
1713 if( ! a->s ) {
1714 echo_message(Error, "Zero pointer as argument! Check your config.h");
1715 return FALSE;
1718 KeySym keysym;
1719 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1720 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1721 return FALSE;
1724 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1725 echo_message(Error, "Couldn't translate keysym to keycode");
1726 return FALSE;
1729 xk.type = KeyPress;
1730 if( !XSendEvent(xdpy, client.state.embed, True, KeyPressMask, (XEvent *)&xk) ) {
1731 echo_message(Error, "XSendEvent failed");
1732 return FALSE;
1734 XFlush(xdpy);
1736 return TRUE;
1739 gboolean
1740 commandhistoryfetch(const Arg *arg) {
1741 State *state = &client.state;
1742 const int length = g_list_length(client.state.commandhistory);
1743 gchar *input_message = NULL;
1745 if (length > 0) {
1746 if (arg->i == DirectionPrev) {
1747 state->commandpointer = (length + state->commandpointer - 1) % length;
1748 } else {
1749 state->commandpointer = (length + state->commandpointer + 1) % length;
1752 const char* command = (char *)g_list_nth_data(state->commandhistory, state->commandpointer);
1753 input_message = g_strconcat(":", command, NULL);
1754 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), input_message);
1755 g_free(input_message);
1756 gtk_editable_set_position(GTK_EDITABLE(client.gui.inputbox), -1);
1757 return TRUE;
1760 return FALSE;
1763 gboolean
1764 bookmark(const Arg *arg) {
1765 FILE *f;
1766 const char *filename;
1767 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1768 const char *title = webkit_web_view_get_title(client.gui.webview);
1769 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1770 f = fopen(filename, "a");
1771 g_free((gpointer *)filename);
1772 if (uri == NULL || strlen(uri) == 0) {
1773 set_error("No URI found to bookmark.");
1774 return FALSE;
1776 if (f != NULL) {
1777 fprintf(f, "%s", uri);
1778 if (title != NULL) {
1779 fprintf(f, "%s", " ");
1780 fprintf(f, "%s", title);
1782 if (arg->s && strlen(arg->s)) {
1783 build_taglist(arg, f);
1785 fprintf(f, "%s", "\n");
1786 fclose(f);
1787 echo_message(Info, "Bookmark saved");
1788 return TRUE;
1789 } else {
1790 set_error("Bookmarks file not found.");
1791 return FALSE;
1795 gboolean
1796 history() {
1797 FILE *f;
1798 const char *filename;
1799 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1800 const char *title = webkit_web_view_get_title(client.gui.webview);
1801 char *entry, buffer[512], *new;
1802 int n, i = 0;
1803 gboolean finished = FALSE;
1804 if (uri != NULL) {
1805 if (title != NULL) {
1806 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1807 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1808 } else {
1809 entry = malloc((strlen(uri) + 1) * sizeof(char));
1810 memset(entry, 0, strlen(uri) + 1);
1812 if (entry != NULL) {
1813 strncpy(entry, uri, strlen(uri));
1814 if (title != NULL) {
1815 strncat(entry, " ", 1);
1816 strncat(entry, title, strlen(title));
1818 n = strlen(entry);
1819 filename = g_strdup_printf(private_mode ? "/dev/null" : HISTORY_STORAGE_FILENAME);
1820 f = fopen(filename, "r");
1821 if (f != NULL) {
1822 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1823 if (new != NULL) {
1824 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1825 /* newest entries go on top */
1826 strncpy(new, entry, strlen(entry));
1827 strncat(new, "\n", 1);
1828 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1829 while (finished != TRUE) {
1830 if ((char *)NULL == fgets(buffer, 512, f)) {
1831 /* check if end of file was reached / error occured */
1832 if (!feof(f)) {
1833 break;
1835 /* end of file reached */
1836 finished = TRUE;
1837 continue;
1839 /* compare line (-1 because of newline character) */
1840 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1841 /* if the URI is already in history; we put it on top and skip it here */
1842 strncat(new, buffer, 512);
1843 i++;
1845 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1846 break;
1849 fclose(f);
1851 f = fopen(filename, "w");
1852 g_free((gpointer *)filename);
1853 if (f != NULL) {
1854 fprintf(f, "%s", new);
1855 fclose(f);
1857 if (new != NULL) {
1858 free(new);
1859 new = NULL;
1863 if (entry != NULL) {
1864 free(entry);
1865 entry = NULL;
1868 return TRUE;
1871 static gboolean
1872 view_source(const Arg * arg) {
1873 gboolean current_mode = webkit_web_view_get_view_source_mode(client.gui.webview);
1874 webkit_web_view_set_view_source_mode(client.gui.webview, !current_mode);
1875 webkit_web_view_reload(client.gui.webview);
1876 return TRUE;
1879 /* open an external editor defined by the protocol handler for
1880 vimprobableedit on a text box or similar */
1881 static gboolean
1882 open_editor(const Arg *arg) {
1883 char *text = NULL;
1884 gboolean success;
1885 GPid child_pid;
1886 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1887 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1888 temp_dir);
1889 int temp_file_handle = -1;
1891 /* check if active element is suitable for text editing */
1892 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1893 if (value == NULL) {
1894 g_free(message);
1895 return FALSE;
1897 tag = g_strdup(value);
1898 if (strcmp(tag, "INPUT") == 0) {
1899 /* extra check: type == text */
1900 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1901 if (strcmp(value, "text") != 0) {
1902 g_free(value);
1903 g_free(message);
1904 return FALSE;
1906 g_free(value);
1907 g_free(message);
1908 } else if (strcmp(tag, "TEXTAREA") != 0) {
1909 g_free(value);
1910 g_free(message);
1911 return FALSE;
1913 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1914 text = g_strdup(value);
1915 if (text == NULL) {
1916 g_free(value);
1917 g_free(message);
1918 return FALSE;
1920 jsapi_evaluate_script("editElem = document.activeElement", &value, &message);
1921 g_free(value);
1922 g_free(message);
1924 /* write text into temporary file */
1925 temp_file_handle = mkstemp(temp_file_name);
1926 if (temp_file_handle == -1) {
1927 message = g_strdup_printf("Could not create temporary file: %s",
1928 strerror(errno));
1929 echo_message(Error, message);
1930 g_free(value);
1931 g_free(message);
1932 g_free(text);
1933 return FALSE;
1935 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1936 message = g_strdup_printf("Short write to temporary file: %s",
1937 strerror(errno));
1938 echo_message(Error, message);
1939 g_free(value);
1940 g_free(message);
1941 g_free(text);
1942 return FALSE;
1944 close(temp_file_handle);
1945 g_free(text);
1947 /* spawn editor */
1948 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1949 success = open_handler_pid(edit_url, &child_pid);
1950 g_free(edit_url);
1951 if (!success) {
1952 echo_message(Error, "External editor open failed (no handler for"
1953 " vimprobableedit protocol?)");
1954 unlink(temp_file_name);
1955 g_free(value);
1956 g_free(message);
1957 return FALSE;
1960 /* mark the active text box as "under processing" */
1961 jsapi_evaluate_script(
1962 "editElem.disabled = true;"
1963 "editElem.originalBackground = "
1964 " editElem.style.background;"
1965 "editElem.style.background = '#aaaaaa';"
1966 ,&value, &message);
1968 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1970 /* temp_file_name is freed in _resume_from_editor */
1971 g_free(value);
1972 g_free(message);
1973 g_free(tag);
1974 return TRUE;
1978 /* pick up from where open_editor left the work to the glib event loop.
1980 This is called when the external editor exits.
1982 The data argument points to allocated memory containing the temporary file
1983 name. */
1984 void
1985 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1986 FILE *fp;
1987 GString *set_value_js = g_string_new(
1988 "editElem.value = \"");
1989 g_spawn_close_pid(child_pid);
1990 gchar *value = NULL, *message = NULL;
1991 gchar *temp_file_name = data;
1992 gchar buffer[BUF_SIZE] = "";
1993 gchar *buf_ptr = buffer;
1994 int char_read;
1996 jsapi_evaluate_script(
1997 "editElem.disabled = true;"
1998 "editElem.style.background = '#aaaaaa';"
1999 ,&value, &message);
2000 g_free(value);
2001 g_free(message);
2003 if (child_status) {
2004 echo_message(Error, "External editor returned with non-zero status,"
2005 " discarding edits.");
2006 goto error_exit;
2009 /* re-read the new contents of the file and put it into the HTML element */
2010 if (!access(temp_file_name, R_OK) == 0) {
2011 message = g_strdup_printf("Could not access temporary file: %s",
2012 strerror(errno));
2013 goto error_exit;
2015 fp = fopen(temp_file_name, "r");
2016 if (fp == NULL) {
2017 /* this would be too weird to even emit an error message */
2018 goto error_exit;
2020 jsapi_evaluate_script("editElem.value = '';",
2021 &value, &message);
2022 g_free(value);
2023 g_free(message);
2025 while (EOF != (char_read = fgetc(fp))) {
2026 if (char_read == '\n') {
2027 *buf_ptr++ = '\\';
2028 *buf_ptr++ = 'n';
2029 } else if (char_read == '"') {
2030 *buf_ptr++ = '\\';
2031 *buf_ptr++ = '"';
2032 } else {
2033 *buf_ptr++ = char_read;
2035 /* ship out as the buffer when space gets tight. This has
2036 fuzz to save on thinking, plus we have enough space for the
2037 trailing "; in any case. */
2038 if (buf_ptr-buffer>=BUF_SIZE-10) {
2039 *buf_ptr = 0;
2040 g_string_append(set_value_js, buffer);
2041 buf_ptr = buffer;
2044 *buf_ptr++ = '"';
2045 *buf_ptr++ = ';';
2046 *buf_ptr = 0;
2047 g_string_append(set_value_js, buffer);
2048 fclose(fp);
2050 jsapi_evaluate_script(set_value_js->str, &value, &message);
2052 /* Fall through, error and normal exit are identical */
2053 error_exit:
2054 jsapi_evaluate_script(
2055 "editElem.disabled = false;"
2056 "editElem.style.background ="
2057 " editElem.originalBackground;"
2058 ,&value, &message);
2060 g_string_free(set_value_js, TRUE);
2061 unlink(temp_file_name);
2062 g_free(temp_file_name);
2063 g_free(value);
2064 g_free(message);
2067 static gboolean
2068 focus_input(const Arg *arg) {
2069 static Arg a;
2071 a.s = g_strdup("hints.focusInput();");
2072 a.i = Silent;
2073 script(&a);
2074 g_free(a.s);
2075 update_state();
2076 client.state.manual_focus = TRUE;
2077 return TRUE;
2080 static void
2081 clear_focus(void) {
2082 static Arg a;
2084 a.s = g_strdup("hints.clearFocus();");
2085 a.i = Silent;
2086 script(&a);
2087 g_free(a.s);
2088 a.i = ModeNormal;
2089 a.s = NULL;
2090 set(&a);
2093 static gboolean
2094 browser_settings(const Arg *arg) {
2095 char line[255];
2096 if (!arg->s) {
2097 set_error("Missing argument.");
2098 return FALSE;
2100 strncpy(line, arg->s, 254);
2101 if (process_set_line(line))
2102 return TRUE;
2103 else {
2104 set_error("Invalid setting.");
2105 return FALSE;
2109 char *
2110 search_word(int whichword) {
2111 int k = 0;
2112 static char word[240];
2113 char *c = my_pair.line;
2115 while (isspace(*c) && *c)
2116 c++;
2118 switch (whichword) {
2119 case 0:
2120 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2121 word[k++] = *c;
2122 c++;
2124 word[k] = '\0';
2125 strncpy(my_pair.what, word, 20);
2126 break;
2127 case 1:
2128 while (*c && k < 240) {
2129 word[k++] = *c;
2130 c++;
2132 word[k] = '\0';
2133 strncpy(my_pair.value, word, 240);
2134 break;
2137 return c;
2140 static gboolean
2141 process_set_line(char *line) {
2142 char *c;
2143 int listlen, i;
2144 gboolean boolval;
2145 WebKitWebSettings *settings;
2147 settings = webkit_web_view_get_settings(client.gui.webview);
2148 my_pair.line = line;
2149 c = search_word(0);
2150 if (!strlen(my_pair.what))
2151 return FALSE;
2153 while (isspace(*c) && *c)
2154 c++;
2156 if (*c == ':' || *c == '=')
2157 c++;
2159 my_pair.line = c;
2160 c = search_word(1);
2162 listlen = LENGTH(browsersettings);
2163 for (i = 0; i < listlen; i++) {
2164 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2165 /* mandatory argument not provided */
2166 if (strlen(my_pair.value) == 0)
2167 return FALSE;
2168 /* process qmark? */
2169 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2170 return (process_save_qmark(my_pair.value, client.gui.webview));
2172 /* interpret boolean values */
2173 if (browsersettings[i].boolval) {
2174 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) {
2175 boolval = TRUE;
2176 } 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) {
2177 boolval = FALSE;
2178 } else {
2179 return FALSE;
2181 } else if (browsersettings[i].colourval) {
2182 /* interpret as hexadecimal colour */
2183 if (!parse_colour(my_pair.value)) {
2184 return FALSE;
2187 if (browsersettings[i].var != NULL) {
2188 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2189 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2190 /* in this case, \0 will not have been copied */
2191 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2192 /* in case this string is also used for a webkit setting, make sure it's consistent */
2193 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2194 echo_message(Info, "String too long; automatically truncated!");
2197 if (strlen(browsersettings[i].webkit) > 0) {
2198 /* activate appropriate webkit setting */
2199 if (browsersettings[i].boolval) {
2200 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2201 } else if (browsersettings[i].intval) {
2202 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2203 } else {
2204 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2206 webkit_web_view_set_settings(client.gui.webview, settings);
2209 if (strlen(my_pair.what) == 14) {
2210 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2211 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2212 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2213 complete_case_sensitive = boolval;
2215 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2216 toggle_proxy(boolval);
2217 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2218 toggle_scrollbars(boolval);
2219 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2220 gtk_widget_set_visible(GTK_WIDGET(client.gui.statusbar), boolval);
2221 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2222 gtk_widget_set_visible(client.gui.inputbox, boolval);
2223 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2224 escape_input_on_load = boolval;
2225 } else if (strlen(my_pair.what) == 7 && strncmp("private", my_pair.what, 7) == 0) {
2226 /* Store the state of the last 'on' cookie state before toggling it off */
2227 if (boolval) {
2228 /* Only update this LastOn state if private mode was false before */
2229 if (!private_mode) {
2230 CookiePolicyLastOn = CookiePolicy;
2232 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
2233 } else {
2234 /* If here, we are not in private_mode, so restore the cookie policy */
2235 CookiePolicy = CookiePolicyLastOn;
2237 soup_cookie_jar_set_accept_policy(client.net.session_cookie_jar, CookiePolicy);
2238 private_mode = boolval; /* Lastly, set the private_mode for use in other areas */
2239 } else if (strlen(my_pair.what) == 7 && strncmp("cookies", my_pair.what, 7) == 0) {
2240 /* cookie policy */
2241 if (strncmp(my_pair.value, "on", 2) == 0 || strncmp(my_pair.value, "true", 4) == 0 ||
2242 strncmp(my_pair.value, "ON", 2) == 0 || strncmp(my_pair.value, "TRUE", 4) == 0 ||
2243 strncmp(my_pair.value, "all", 3) == 0 || strncmp(my_pair.value, "ALL", 3) == 0) {
2244 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
2245 } else if (strncmp(my_pair.value, "off", 3) == 0 || strncmp(my_pair.value, "false", 5) == 0 ||
2246 strncmp(my_pair.value, "OFF", 3) == 0 || strncmp(my_pair.value, "FALSE", 5) == 0 ||
2247 strncmp(my_pair.value, "never", 5) == 0 || strncmp(my_pair.value, "NEVER", 5) == 5 ||
2248 strncmp(my_pair.value, "none", 4) == 0 || strncmp(my_pair.value, "NONE", 4) == 0) {
2249 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
2250 } else if (strncmp(my_pair.value, "origin", 6) == 0 || strncmp(my_pair.value, "ORIGIN", 6) == 0 ||
2251 strncmp(my_pair.value, "no_third", 8) == 0 || strncmp(my_pair.value, "NO_THIRD", 8) == 0 ||
2252 strncmp(my_pair.value, "no third", 8) == 0 || strncmp(my_pair.value, "NO THIRD", 8) == 0) {
2253 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
2254 } else {
2255 return FALSE;
2257 soup_cookie_jar_set_accept_policy(client.net.session_cookie_jar, CookiePolicy);
2260 /* SSL certificate checking */
2261 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2262 if (boolval) {
2263 strict_ssl = TRUE;
2264 g_object_set(G_OBJECT(client.net.session), "ssl-strict", TRUE, NULL);
2265 } else {
2266 strict_ssl = FALSE;
2267 g_object_set(G_OBJECT(client.net.session), "ssl-strict", FALSE, NULL);
2270 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2271 g_object_set(G_OBJECT(client.net.session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2273 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2274 set_default_winsize(my_pair.value);
2277 /* reload page? */
2278 if (browsersettings[i].reload)
2279 webkit_web_view_reload(client.gui.webview);
2280 return TRUE;
2283 return FALSE;
2286 gboolean
2287 process_line(char *line) {
2288 char *c = line, *command_hist;
2289 int i;
2290 size_t len, length = strlen(line);
2291 gboolean found = FALSE, success = FALSE;
2292 Arg a;
2293 GList *l;
2295 while (isspace(*c))
2296 c++;
2297 /* Ignore blank lines. */
2298 if (c[0] == '\0')
2299 return TRUE;
2301 command_hist = g_strdup(c);
2303 /* check for colon command aliases first */
2304 for (l = client.config.colon_aliases; l; l = g_list_next(l)) {
2305 Alias *alias = (Alias *)l->data;
2306 if (length == strlen(alias->alias) && strncmp(alias->alias, line, length) == 0) {
2307 /* reroute to target command */
2308 c = alias->target;
2309 length = strlen(alias->target);
2310 break;
2314 /* check standard commands */
2315 for (i = 0; i < LENGTH(commands); i++) {
2316 if (commands[i].cmd == NULL)
2317 break;
2318 len = strlen(commands[i].cmd);
2319 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2320 found = TRUE;
2321 a.i = commands[i].arg.i;
2322 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2323 success = commands[i].func(&a);
2324 g_free(a.s);
2325 break;
2329 save_command_history(command_hist);
2330 g_free(command_hist);
2332 if (!found) {
2333 echo_message(Error, "Not a browser command: %s", c);
2334 } else if (!success) {
2335 if (client.state.error_msg != NULL) {
2336 echo_message(Error, client.state.error_msg);
2337 g_free(client.state.error_msg);
2338 client.state.error_msg = NULL;
2339 } else {
2340 echo_message(Error, "Unknown error. Please file a bug report!");
2343 return success;
2346 static gboolean
2347 search_tag(const Arg * a) {
2348 FILE *f;
2349 const char *filename;
2350 const char *tag = a->s;
2351 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2352 int t, i, intag, k;
2354 if (!tag) {
2355 /* The user must give us something to load up. */
2356 set_error("Bookmark tag required with this option.");
2357 return FALSE;
2360 if (strlen(tag) > MAXTAGSIZE) {
2361 set_error("Tag too long.");
2362 return FALSE;
2365 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2366 f = fopen(filename, "r");
2367 g_free((gpointer *)filename);
2368 if (f == NULL) {
2369 set_error("Couldn't open bookmarks file.");
2370 return FALSE;
2372 while (fgets(s, BUFFERSIZE-1, f)) {
2373 intag = 0;
2374 t = strlen(s) - 1;
2375 while (isspace(s[t]))
2376 t--;
2377 if (s[t] != ']') continue;
2378 while (t > 0) {
2379 if (s[t] == ']') {
2380 if (!intag)
2381 intag = t;
2382 else
2383 intag = 0;
2384 } else {
2385 if (s[t] == '[') {
2386 if (intag) {
2387 i = 0;
2388 k = t + 1;
2389 while (k < intag)
2390 foundtag[i++] = s[k++];
2391 foundtag[i] = '\0';
2392 /* foundtag now contains the tag */
2393 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2394 i = 0;
2395 while (isspace(s[i])) i++;
2396 k = 0;
2397 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2398 url[k] = '\0';
2399 Arg x = { .i = TargetNew, .s = url };
2400 open_arg(&x);
2403 intag = 0;
2406 t--;
2409 return TRUE;
2412 void
2413 toggle_proxy(gboolean onoff) {
2414 SoupURI *proxy_uri;
2415 char *filename, *new;
2417 if (onoff == FALSE) {
2418 g_object_set(client.net.session, "proxy-uri", NULL, NULL);
2419 } else {
2420 filename = (char *)g_getenv("http_proxy");
2422 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2423 * defined.
2425 if (filename == NULL)
2426 filename = (char *)g_getenv("HTTP_PROXY");
2428 if (filename != NULL && 0 < strlen(filename)) {
2429 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2430 proxy_uri = soup_uri_new(new);
2432 g_object_set(client.net.session, "proxy-uri", proxy_uri, NULL);
2434 soup_uri_free(proxy_uri);
2435 g_free(new);
2440 void
2441 toggle_scrollbars(gboolean onoff) {
2442 Gui *gui = &client.gui;
2443 if (onoff == TRUE) {
2444 gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2445 gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2446 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2447 } else {
2448 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2449 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2450 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2452 gtk_widget_set_scroll_adjustments (GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2454 return;
2457 void set_default_winsize(const char * const size) {
2458 char *p;
2459 int x = 640, y = 480;
2461 x = strtol(size, &p, 10);
2462 if (errno == ERANGE || x <= 0) {
2463 x = 640;
2464 goto out;
2467 if (p == size || strlen(size) == p - size)
2468 goto out;
2470 y = strtol(p + 1, NULL, 10);
2471 if (errno == ERANGE || y <= 0)
2472 y = 480;
2474 out:
2475 gtk_window_resize(GTK_WINDOW(client.gui.window), x, y);
2478 void
2479 update_url(const char *uri) {
2480 Gui *gui = &client.gui;
2481 gboolean ssl = g_str_has_prefix(uri, "https://");
2482 GdkColor color;
2483 WebKitWebFrame *frame;
2484 WebKitWebDataSource *src;
2485 WebKitNetworkRequest *request;
2486 SoupMessage *msg;
2487 gboolean ssl_ok;
2488 char *sslactivecolor;
2489 gchar *markup;
2490 #ifdef ENABLE_HISTORY_INDICATOR
2491 char before[] = " [";
2492 char after[] = "]";
2493 gboolean back = webkit_web_view_can_go_back(gui->webview);
2494 gboolean fwd = webkit_web_view_can_go_forward(gui->webview);
2496 if (!back && !fwd)
2497 before[0] = after[0] = '\0';
2498 #endif
2499 markup = g_markup_printf_escaped(
2500 #ifdef ENABLE_HISTORY_INDICATOR
2501 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2502 before, back ? "+" : "", fwd ? "-" : "", after
2503 #else
2504 "<span font=\"%s\">%s</span>", statusfont, uri
2505 #endif
2507 gtk_label_set_markup(GTK_LABEL(gui->status_url), markup);
2508 g_free(markup);
2509 if (ssl) {
2510 frame = webkit_web_view_get_main_frame(gui->webview);
2511 src = webkit_web_frame_get_data_source(frame);
2512 request = webkit_web_data_source_get_request(src);
2513 msg = webkit_network_request_get_message(request);
2514 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2515 if (ssl_ok)
2516 sslactivecolor = sslbgcolor;
2517 else
2518 sslactivecolor = sslinvalidbgcolor;
2520 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2521 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &color);
2522 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2523 gtk_widget_modify_fg(GTK_WIDGET(gui->status_url), GTK_STATE_NORMAL, &color);
2524 gtk_widget_modify_fg(GTK_WIDGET(gui->status_state), GTK_STATE_NORMAL, &color);
2527 void
2528 update_state() {
2529 State* state = &client.state;
2530 char *markup;
2531 int download_count = g_list_length(state->activeDownloads);
2532 GString *status = g_string_new("");
2534 /* construct the status line */
2536 /* count, modkey and input buffer */
2537 g_string_append_printf(status, "%.0d", state->count);
2538 if (state->current_modkey) g_string_append_c(status, state->current_modkey);
2540 /* the number of active downloads */
2541 if (state->activeDownloads) {
2542 g_string_append_printf(status, " %d active %s", download_count,
2543 (download_count == 1) ? "download" : "downloads");
2546 #ifdef ENABLE_WGET_PROGRESS_BAR
2547 /* the progressbar */
2549 int progress = -1;
2550 char progressbar[progressbartick + 1];
2552 if (state->activeDownloads) {
2553 progress = 0;
2554 GList *ptr;
2556 for (ptr = state->activeDownloads; ptr; ptr = g_list_next(ptr)) {
2557 progress += 100 * webkit_download_get_progress(ptr->data);
2560 progress /= download_count;
2562 } else if (webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FINISHED
2563 && webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FAILED) {
2565 progress = webkit_web_view_get_progress(client.gui.webview) * 100;
2568 if (progress >= 0) {
2569 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2570 g_string_append_printf(status, " %c%s%c",
2571 progressborderleft, progressbar, progressborderright);
2574 #endif
2576 /* and the current scroll position */
2578 int max = gtk_adjustment_get_upper(client.gui.adjust_v) - gtk_adjustment_get_page_size(client.gui.adjust_v);
2579 int val = (int)(gtk_adjustment_get_value(client.gui.adjust_v) / max * 100);
2581 if (max == 0)
2582 g_string_append(status, " All");
2583 else if (val == 0)
2584 g_string_append(status, " Top");
2585 else if (val == 100)
2586 g_string_append(status, " Bot");
2587 else
2588 g_string_append_printf(status, " %d%%", val);
2592 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2593 gtk_label_set_markup(GTK_LABEL(client.gui.status_state), markup);
2594 g_free(markup);
2596 g_string_free(status, TRUE);
2599 static void
2600 setup_client(void) {
2601 State *state = &client.state;
2602 Config *config = &client.config;
2604 state->mode = ModeNormal;
2605 state->count = 0;
2606 state->rememberedURI[0] = '\0';
2607 state->manual_focus = FALSE;
2608 state->is_inspecting = FALSE;
2609 state->commandhistory = NULL;
2610 state->commandpointer = 0;
2612 config->colon_aliases = NULL;
2613 config->cookie_timeout = 4800;
2616 void
2617 setup_modkeys() {
2618 unsigned int i;
2619 client.config.modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2620 char *ptr = client.config.modkeys;
2622 for (i = 0; i < LENGTH(keys); i++)
2623 if (keys[i].modkey && !strchr(client.config.modkeys, keys[i].modkey))
2624 *(ptr++) = keys[i].modkey;
2625 client.config.modkeys = realloc(client.config.modkeys, &ptr[0] - &client.config.modkeys[0] + 1);
2628 void
2629 setup_gui() {
2630 Gui *gui = &client.gui;
2631 State *state = &client.state;
2633 gui->scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2634 gui->scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2635 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2636 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2637 if (client.state.embed) {
2638 gui->window = GTK_WINDOW(gtk_plug_new(client.state.embed));
2639 } else {
2640 gui->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2641 gtk_window_set_wmclass(GTK_WINDOW(gui->window), "vimprobable2", "Vimprobable2");
2643 gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
2644 gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2645 gui->inputbox = gtk_entry_new();
2646 gui->webview = (WebKitWebView*)webkit_web_view_new();
2647 gui->statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2648 gui->eventbox = gtk_event_box_new();
2649 gui->status_url = gtk_label_new(NULL);
2650 gui->status_state = gtk_label_new(NULL);
2651 GdkColor bg;
2652 PangoFontDescription *font;
2653 GdkGeometry hints = { 1, 1 };
2654 gui->inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gui->webview));
2656 state->clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2657 state->clipboards[1] = gtk_clipboard_get(GDK_NONE);
2658 setup_settings();
2659 gdk_color_parse(statusbgcolor, &bg);
2660 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
2661 gtk_widget_set_name(GTK_WIDGET(gui->window), "Vimprobable2");
2662 gtk_window_set_geometry_hints(gui->window, NULL, &hints, GDK_HINT_MIN_SIZE);
2664 state->keymap = gdk_keymap_get_default();
2666 #ifdef DISABLE_SCROLLBAR
2667 gui->viewport = gtk_scrolled_window_new(NULL, NULL);
2668 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2669 #else
2670 /* Ensure we still see scrollbars. */
2671 gui->viewport = gtk_scrolled_window_new(gui->adjust_h, gui->adjust_v);
2672 #endif
2674 gui->pane = gtk_vpaned_new();
2675 gtk_paned_pack1(GTK_PANED(gui->pane), GTK_WIDGET(gui->box), TRUE, TRUE);
2677 setup_signals();
2678 gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
2680 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2681 * titlebars, we can still scroll.
2683 gtk_widget_set_scroll_adjustments(GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2685 font = pango_font_description_from_string(urlboxfont[0]);
2686 gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
2687 pango_font_description_free(font);
2688 gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
2689 gtk_misc_set_alignment(GTK_MISC(gui->status_url), 0.0, 0.0);
2690 gtk_misc_set_alignment(GTK_MISC(gui->status_state), 1.0, 0.0);
2691 gtk_box_pack_start(gui->statusbar, gui->status_url, TRUE, TRUE, 2);
2692 gtk_box_pack_start(gui->statusbar, gui->status_state, FALSE, FALSE, 2);
2693 gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar));
2694 gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
2695 gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
2696 gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
2697 gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
2698 gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane));
2699 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
2700 gtk_widget_show_all(GTK_WIDGET(gui->window));
2701 set_widget_font_and_color(gui->inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2702 g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2705 void
2706 setup_settings() {
2707 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2708 char *filename, *file_url;
2710 client.net.session = webkit_get_default_session();
2711 g_object_set(G_OBJECT(client.net.session), "ssl-ca-file", ca_bundle, NULL);
2712 g_object_set(G_OBJECT(client.net.session), "ssl-strict", strict_ssl, NULL);
2713 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2714 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2715 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2716 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2717 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2718 g_object_set(G_OBJECT(settings), "enable-html5-local-storage", enableLocalstorage, NULL);
2719 g_object_set(G_OBJECT(settings), "enable-html5-database", enableDatabase, NULL);
2720 g_object_set(G_OBJECT(settings), "javascript-can-open-windows-automatically", javascriptPopups, NULL);
2721 filename = g_strdup_printf(USER_STYLESHEET);
2722 file_url = g_strdup_printf("file://%s", filename);
2723 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2724 g_free(file_url);
2725 g_free(filename);
2726 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2727 g_object_get(G_OBJECT(settings), "zoom-step", &client.config.zoomstep, NULL);
2728 webkit_web_view_set_settings(client.gui.webview, settings);
2730 /* proxy */
2731 toggle_proxy(use_proxy);
2734 void
2735 setup_signals() {
2736 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
2737 #ifdef ENABLE_COOKIE_SUPPORT
2738 /* Headers. */
2739 g_signal_connect_after(G_OBJECT(client.net.session), "request-started", G_CALLBACK(new_generic_request), NULL);
2740 #endif
2741 /* Accept-language header */
2742 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2743 /* window */
2744 g_object_connect(G_OBJECT(client.gui.window),
2745 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2746 NULL);
2747 /* frame */
2748 g_signal_connect(G_OBJECT(frame),
2749 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2750 /* webview */
2751 g_object_connect(G_OBJECT(client.gui.webview),
2752 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2753 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2754 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2755 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2756 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2757 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2758 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2759 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2760 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2761 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2762 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2763 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2764 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2765 NULL);
2766 /* webview adjustment */
2767 g_object_connect(G_OBJECT(client.gui.adjust_v),
2768 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2769 NULL);
2770 /* inputbox */
2771 g_object_connect(G_OBJECT(client.gui.inputbox),
2772 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2773 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2774 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2775 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2776 NULL);
2777 /* inspector */
2778 g_signal_connect(G_OBJECT(client.gui.inspector),
2779 "inspect-web-view", G_CALLBACK(inspector_new_cb), NULL);
2780 g_signal_connect(G_OBJECT(client.gui.inspector),
2781 "show-window", G_CALLBACK(inspector_show_cb), NULL);
2782 g_signal_connect(G_OBJECT(client.gui.inspector),
2783 "close-window", G_CALLBACK(inspector_close_cb), NULL);
2784 g_signal_connect(G_OBJECT(client.gui.inspector),
2785 "finished", G_CALLBACK(inspector_finished_cb), NULL);
2788 #ifdef ENABLE_USER_SCRIPTFILE
2789 static void
2790 scripts_run_user_file() {
2791 gchar *js = NULL, *user_scriptfile = NULL;
2792 GError *error = NULL;
2794 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2796 /* run the users script file */
2797 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2798 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2800 gchar *value = NULL, *message = NULL;
2802 jsapi_evaluate_script(js, &value, &message);
2803 g_free(js);
2804 if (message) {
2805 fprintf(stderr, "%s", message);
2807 g_free(value);
2808 g_free(message);
2809 } else {
2810 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2813 g_free(user_scriptfile);
2815 #endif
2817 #ifdef ENABLE_COOKIE_SUPPORT
2818 void
2819 setup_cookies()
2821 Network *net = &client.net;
2822 if (net->file_cookie_jar)
2823 g_object_unref(net->file_cookie_jar);
2825 if (net->session_cookie_jar)
2826 g_object_unref(net->session_cookie_jar);
2828 net->session_cookie_jar = soup_cookie_jar_new();
2829 soup_cookie_jar_set_accept_policy(net->session_cookie_jar, CookiePolicy);
2831 net->cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2833 load_all_cookies();
2835 g_signal_connect(G_OBJECT(net->file_cookie_jar), "changed",
2836 G_CALLBACK(update_cookie_jar), NULL);
2839 /* This function could be used for any header requests we receive
2840 * for not, it's limited to handling cookies
2842 void
2843 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2845 SoupMessageHeaders *soup_msg_h;
2846 SoupURI *uri;
2847 char *cookie_str;
2849 soup_msg_h = soup_msg->request_headers;
2850 soup_message_headers_remove(soup_msg_h, "Cookie");
2851 uri = soup_message_get_uri(soup_msg);
2852 soup_message_set_first_party(soup_msg, uri);
2853 if ((cookie_str = get_cookies(uri))) {
2854 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2855 g_free(cookie_str);
2858 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_response_headers), NULL);
2860 return;
2863 char *
2864 get_cookies(SoupURI *soup_uri) {
2865 char *cookie_str;
2867 cookie_str = soup_cookie_jar_get_cookies(client.net.file_cookie_jar, soup_uri, TRUE);
2869 return cookie_str;
2872 void
2873 handle_response_headers(SoupMessage *soup_msg, gpointer unused)
2875 GSList *resp_cookie = NULL, *cookie_list;
2876 SoupCookie *cookie;
2877 SoupURI *uri = soup_message_get_uri(soup_msg);
2879 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_NEVER) {
2880 cookie_list = soup_cookies_from_response(soup_msg);
2881 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2883 SoupDate *soup_date;
2884 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2886 if (client.config.cookie_timeout && cookie->expires == NULL) {
2887 soup_date = soup_date_new_from_time_t(time(NULL) + client.config.cookie_timeout * 10);
2888 soup_cookie_set_expires(cookie, soup_date);
2889 soup_date_free(soup_date);
2891 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_ALWAYS) {
2892 /* no third party cookies: for libsoup 2.4 and later, the following should work */
2893 /*soup_cookie_jar_add_cookie_with_first_party(client.net.file_cookie_jar, uri, cookie);*/
2894 if (strcmp(soup_uri_get_host(uri), soup_cookie_get_domain(cookie)) == 0) {
2895 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
2897 } else {
2898 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
2902 soup_cookies_free(cookie_list);
2905 return;
2908 void
2909 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2911 if (!new) {
2912 /* Nothing to do. */
2913 return;
2916 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_NEVER) {
2917 SoupCookie *copy;
2918 copy = soup_cookie_copy(new);
2920 soup_cookie_jar_add_cookie(client.net.session_cookie_jar, copy);
2923 return;
2926 void
2927 load_all_cookies(void)
2929 Network *net = &client.net;
2930 GSList *cookie_list;
2931 net->file_cookie_jar = soup_cookie_jar_text_new(net->cookie_store, COOKIES_STORAGE_READONLY);
2933 /* Put them back in the session store. */
2934 GSList *cookies_from_file = soup_cookie_jar_all_cookies(net->file_cookie_jar);
2935 cookie_list = cookies_from_file;
2937 for (; cookies_from_file;
2938 cookies_from_file = cookies_from_file->next)
2940 soup_cookie_jar_add_cookie(net->session_cookie_jar, cookies_from_file->data);
2943 soup_cookies_free(cookies_from_file);
2944 g_slist_free(cookie_list);
2946 return;
2949 #endif
2951 void
2952 mop_up(void) {
2953 /* Free up any nasty globals before exiting. */
2954 #ifdef ENABLE_COOKIE_SUPPORT
2955 if (client.net.cookie_store)
2956 g_free(client.net.cookie_store);
2957 #endif
2958 return;
2962 main(int argc, char *argv[]) {
2963 static Arg a;
2964 static char url[256] = "";
2965 static gboolean ver = false;
2966 static gboolean configfile_exists = FALSE;
2967 static const char *cfile = NULL;
2968 static gchar *winid = NULL;
2969 static GOptionEntry opts[] = {
2970 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2971 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2972 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2973 { NULL }
2975 static GError *err;
2976 args = argv;
2977 Config *config = &client.config;
2979 /* command line argument: version */
2980 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2981 g_printerr("can't init gtk: %s\n", err->message);
2982 g_error_free(err);
2983 return EXIT_FAILURE;
2986 if (ver) {
2987 printf("%s\n", INTERNAL_VERSION);
2988 return EXIT_SUCCESS;
2991 setup_client();
2993 if (getenv("TMPDIR")) {
2994 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2995 temp_dir[MAX_SETTING_SIZE-1] = 0;
2998 if( getenv("XDG_CONFIG_HOME") )
2999 config->config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
3000 else
3001 config->config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
3003 sprintf(downloads_path, "%s", getenv("HOME"));
3005 if (cfile)
3006 config->configfile = g_strdup(cfile);
3007 else
3008 config->configfile = g_strdup_printf(RCFILE);
3010 if (!g_thread_supported())
3011 g_thread_init(NULL);
3013 if (winid) {
3014 if (strncmp(winid, "0x", 2) == 0) {
3015 client.state.embed = strtol(winid, NULL, 16);
3016 } else {
3017 client.state.embed = atoi(winid);
3021 setup_modkeys();
3022 make_keyslist();
3023 setup_gui();
3024 #ifdef ENABLE_COOKIE_SUPPORT
3025 setup_cookies();
3026 #endif
3028 make_searchengines_list(searchengines, LENGTH(searchengines));
3029 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
3031 /* Check if the specified file exists. */
3032 /* And only warn the user, if they explicitly asked for a config on the
3033 * command line.
3035 if (!(access(config->configfile, F_OK) == 0) && cfile) {
3036 echo_message(Info, "Config file '%s' doesn't exist", cfile);
3037 } else if ((access(config->configfile, F_OK) == 0))
3038 configfile_exists = true;
3040 /* read config file */
3041 /* But only report errors if we failed, and the file existed. */
3042 if ((SUCCESS != read_rcfile(config->configfile)) && configfile_exists) {
3043 echo_message(Error, "Error in config file '%s'", config->configfile);
3044 g_free(config->configfile);
3047 /* command line argument: URL */
3048 if (argc > 1) {
3049 strncpy(url, argv[argc - 1], 255);
3050 } else {
3051 strncpy(url, startpage, 255);
3054 a.i = TargetCurrent;
3055 a.s = url;
3056 open_arg(&a);
3057 gtk_main();
3059 mop_up();
3061 return EXIT_SUCCESS;