Fixed wrong placed #ifdef ENABLE_INCREMENTAL_SEARCH (patch by
[vimprobable2.git] / main.c
blob8ed0a184edc02b303077525338340538588f1171
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2012 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/types.h>
15 #include <sys/wait.h>
16 #include <errno.h>
17 #include <stdlib.h>
18 #include "includes.h"
19 #include "vimprobable.h"
20 #include "utilities.h"
21 #include "callbacks.h"
22 #include "javascript.h"
24 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
25 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
26 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
28 /* remove unused bits, numlock symbol and buttons from keymask */
29 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
31 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
32 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
33 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
35 /* callbacks here */
36 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
37 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
38 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
39 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
40 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
41 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
42 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
43 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
44 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
45 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
46 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
47 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
48 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
49 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
50 static void webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec);
51 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
52 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
53 static WebKitWebView* webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
54 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
55 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
56 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
57 static gboolean blank_cb(void);
59 /* functions */
60 static gboolean bookmark(const Arg *arg);
61 static gboolean browser_settings(const Arg *arg);
62 static gboolean commandhistoryfetch(const Arg *arg);
63 static gboolean complete(const Arg *arg);
64 static gboolean descend(const Arg *arg);
65 gboolean echo(const Arg *arg);
66 static gboolean focus_input(const Arg *arg);
67 static gboolean open_editor(const Arg *arg);
68 void _resume_from_editor(GPid child_pid, int status, gpointer data);
69 static gboolean input(const Arg *arg);
70 static gboolean open_inspector(const Arg * arg);
71 static gboolean navigate(const Arg *arg);
72 static gboolean number(const Arg *arg);
73 static gboolean open_arg(const Arg *arg);
74 static gboolean open_remembered(const Arg *arg);
75 static gboolean paste(const Arg *arg);
76 static gboolean quickmark(const Arg *arg);
77 static gboolean quit(const Arg *arg);
78 static gboolean revive(const Arg *arg);
79 static gboolean print_frame(const Arg *arg);
80 static gboolean search(const Arg *arg);
81 static gboolean set(const Arg *arg);
82 static gboolean script(const Arg *arg);
83 static gboolean scroll(const Arg *arg);
84 static gboolean search_tag(const Arg *arg);
85 static gboolean yank(const Arg *arg);
86 static gboolean view_source(const Arg * arg);
87 static gboolean zoom(const Arg *arg);
88 static gboolean fake_key_event(const Arg *arg);
90 static void clear_focus(void);
91 static void update_url(const char *uri);
92 static void setup_client(void);
93 static void setup_modkeys(void);
94 static void setup_gui(void);
95 static void setup_settings(void);
96 static void setup_signals(void);
97 static void ascii_bar(int total, int state, char *string);
98 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
99 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
100 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
101 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
102 const char *bg_color_str, const char *fg_color_str);
103 static void scripts_run_user_file(void);
105 static gboolean history(void);
106 static gboolean process_set_line(char *line);
107 void save_command_history(char *line);
108 void toggle_proxy(gboolean onoff);
109 void toggle_scrollbars(gboolean onoff);
110 void set_default_winsize(const char * const size);
112 gboolean process_keypress(GdkEventKey *event);
113 void fill_suggline(char * suggline, const char * command, const char *fill_with);
114 GtkWidget * fill_eventbox(const char * completion_line);
115 static void mop_up(void);
117 #include "main.h"
119 /* variables */
120 static char **args;
122 #include "config.h"
123 #include "keymap.h"
125 /* Cookie support. */
126 #ifdef ENABLE_COOKIE_SUPPORT
127 static void setup_cookies(void);
128 static char *get_cookies(SoupURI *soup_uri);
129 static void load_all_cookies(void);
130 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
131 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
132 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
133 #endif
135 Client client;
137 /* callbacks */
138 void
139 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
140 quit(NULL);
143 void
144 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
145 gtk_window_set_title(client.gui.window, title);
148 void
149 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
150 #ifdef ENABLE_GTK_PROGRESS_BAR
151 gtk_entry_set_progress_fraction(GTK_ENTRY(client.gui.inputbox), progress == 100 ? 0 : (double)progress/100);
152 #endif
153 update_state();
156 #ifdef ENABLE_WGET_PROGRESS_BAR
157 void
158 ascii_bar(int total, int state, char *string) {
159 int i;
161 for (i = 0; i < state; i++)
162 string[i] = progressbartickchar;
163 string[i++] = progressbarcurrent;
164 for (; i < total; i++)
165 string[i] = progressbarspacer;
166 string[i] = '\0';
168 #endif
170 void
171 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
172 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
173 const char *uri = webkit_web_view_get_uri(webview);
175 update_url(uri);
176 script(&a);
177 g_free(a.s);
178 scripts_run_user_file();
180 if (client.state.mode == ModeInsert || client.state.mode == ModeHints) {
181 Arg a = { .i = ModeNormal };
182 set(&a);
184 client.state.manual_focus = FALSE;
187 void
188 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
189 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
190 gboolean scripts;
192 g_object_get(settings, "enable-scripts", &scripts, NULL);
193 if (escape_input_on_load && scripts && !client.state.manual_focus && !gtk_widget_is_focus(client.gui.inputbox)) {
194 clear_focus();
196 if (HISTORY_MAX_ENTRIES > 0)
197 history();
198 update_state();
201 void
202 webview_open_js_window_cb(WebKitWebView* temp_view, GParamSpec param_spec) {
203 /* retrieve the URI of the temporary webview */
204 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(temp_view) };
205 /* clean up */
206 webkit_web_view_stop_loading(temp_view);
207 gtk_widget_destroy(GTK_WIDGET(temp_view));
208 /* open the requested window */
209 open_arg(&a);
212 static WebKitWebView *
213 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
214 if (client.state.rememberedURI != NULL && strlen(client.state.rememberedURI) > 0) {
215 if (strncmp(client.state.rememberedURI, "javascript:", 11) != 0) {
216 Arg a = { .i = TargetNew, .s = client.state.rememberedURI };
217 open_arg(&a);
218 return NULL;
221 /* create a temporary webview to execute the script in */
222 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
223 /* wait until the new webview receives its new URI */
224 g_object_connect(temp_view, "signal::notify::uri", G_CALLBACK(webview_open_js_window_cb), NULL, NULL);
225 return temp_view;
228 gboolean
229 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
230 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
231 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
232 open_arg(&a);
233 webkit_web_policy_decision_ignore(decision);
234 return TRUE;
237 gboolean
238 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
239 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
240 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
241 webkit_web_policy_decision_download(decision);
242 return TRUE;
243 } else {
244 return FALSE;
248 static WebKitWebView*
249 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
250 gchar* inspector_title;
251 GtkWidget* inspector_window;
252 GtkWidget* inspector_view;
254 /* just enough code to show the inspector - no signal handling etc. */
255 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
256 if (client.state.embed) {
257 inspector_window = gtk_plug_new(client.state.embed);
258 } else {
259 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
260 gtk_window_set_wmclass(client.gui.window, "vimprobable2", "Vimprobable2");
262 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
263 g_free(inspector_title);
264 inspector_view = webkit_web_view_new();
265 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
266 gtk_widget_show_all(inspector_window);
267 return WEBKIT_WEB_VIEW(inspector_view);
270 gboolean
271 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
272 const gchar *filename;
273 gchar *uri, *path;
274 uint32_t size;
275 WebKitDownloadStatus status;
277 filename = webkit_download_get_suggested_filename(download);
278 if (filename == NULL || strlen(filename) == 0) {
279 filename = "vimprobable_download";
281 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
282 uri = g_strconcat("file://", path, NULL);
283 webkit_download_set_destination_uri(download, uri);
284 g_free(path);
285 g_free(uri);
286 size = (uint32_t)webkit_download_get_total_size(download);
287 if (size > 0)
288 echo_message(Info, "Download %s started (expected size: %u bytes)...", filename, size);
289 else
290 echo_message(Info, "Download %s started (unknown size)...", filename);
291 client.state.activeDownloads = g_list_prepend(client.state.activeDownloads, download);
292 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
293 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
294 status = webkit_download_get_status(download);
295 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
296 webkit_download_start(download);
297 update_state();
298 return TRUE;
301 gboolean
302 blank_cb(void) {
303 return TRUE;
306 void
307 download_progress(WebKitDownload *d, GParamSpec *pspec) {
308 WebKitDownloadStatus status = webkit_download_get_status(d);
310 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
311 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
312 echo_message(Error, "Error while downloading %s", webkit_download_get_suggested_filename(d));
313 } else {
314 echo_message(Info, "Download %s finished", webkit_download_get_suggested_filename(d));
316 client.state.activeDownloads = g_list_remove(client.state.activeDownloads, d);
318 update_state();
322 gboolean
323 process_keypress(GdkEventKey *event) {
324 State *state = &client.state;
325 KeyList *current;
326 guint keyval;
327 GdkModifierType irrelevant;
329 /* Get a mask of modifiers that shouldn't be considered for this event.
330 * E.g.: It shouldn't matter whether ';' is shifted or not. */
331 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
332 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
334 current = client.config.keylistroot;
336 while (current != NULL) {
337 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
338 && (current->Element.modkey == state->current_modkey
339 || (!current->Element.modkey && !state->current_modkey)
340 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
341 && current->Element.key == keyval
342 && current->Element.func)
343 if (current->Element.func(&current->Element.arg)) {
344 state->current_modkey = state->count = 0;
345 update_state();
346 return TRUE;
348 current = current->next;
350 return FALSE;
353 gboolean
354 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
355 State *state = &client.state;
356 Arg a = { .i = ModeNormal, .s = NULL };
357 guint keyval;
358 GdkModifierType irrelevant;
360 /* Get a mask of modifiers that shouldn't be considered for this event.
361 * E.g.: It shouldn't matter whether ';' is shifted or not. */
362 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
363 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
365 switch (state->mode) {
366 case ModeNormal:
367 if ((CLEAN(event->state) & ~irrelevant) == 0) {
368 if (IS_ESCAPE(event)) {
369 echo_message(Info, "");
370 g_free(a.s);
371 } else if (state->current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
372 || (event->keyval == GDK_0 && state->count))) {
373 state->count = (state->count ? state->count * 10 : 0) + (event->keyval - GDK_0);
374 update_state();
375 return TRUE;
376 } else if (strchr(client.config.modkeys, event->keyval) && state->current_modkey != event->keyval) {
377 state->current_modkey = event->keyval;
378 update_state();
379 return TRUE;
382 /* keybindings */
383 if (process_keypress(event) == TRUE) return TRUE;
385 break;
386 case ModeInsert:
387 if (IS_ESCAPE(event)) {
388 a.i = Silent;
389 a.s = g_strdup("hints.clearFocus();");
390 script(&a);
391 g_free(a.s);
392 a.i = ModeNormal;
393 return set(&a);
394 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
395 /* keybindings of non-printable characters */
396 if (process_keypress(event) == TRUE) return TRUE;
398 case ModePassThrough:
399 if (IS_ESCAPE(event)) {
400 echo_message(Info, "");
401 set(&a);
402 return TRUE;
404 break;
405 case ModeSendKey:
406 echo_message(Info, "");
407 set(&a);
408 break;
410 return FALSE;
413 void
414 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
415 const char *fg_color_str) {
416 GdkColor fg_color;
417 GdkColor bg_color;
418 PangoFontDescription *font;
420 font = pango_font_description_from_string(font_str);
421 gtk_widget_modify_font(widget, font);
422 pango_font_description_free(font);
424 if (fg_color_str)
425 gdk_color_parse(fg_color_str, &fg_color);
426 if (bg_color_str)
427 gdk_color_parse(bg_color_str, &bg_color);
429 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
430 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
432 return;
435 void
436 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
437 const char *uri = webkit_web_view_get_uri(webview);
438 char *markup;
440 memset(client.state.rememberedURI, 0, BUF_SIZE);
441 if (link) {
442 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
443 gtk_label_set_markup(GTK_LABEL(client.gui.status_url), markup);
444 strncpy(client.state.rememberedURI, link, BUF_SIZE);
445 g_free(markup);
446 } else
447 update_url(uri);
450 gboolean
451 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
452 Arg a;
454 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
455 if (gtk_window_has_toplevel_focus(client.gui.window)) {
456 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
457 a.i = ModeNormal;
458 return set(&a);
459 } else if (!strcmp(message, "insertmode_on")) {
460 a.i = ModeInsert;
461 return set(&a);
464 return FALSE;
467 void
468 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
469 Gui *gui = &client.gui;
470 State *state = &client.state;
471 char *text;
472 guint16 length = gtk_entry_get_text_length(entry);
473 Arg a;
474 gboolean forward = FALSE;
476 a.i = HideCompletion;
477 complete(&a);
478 if (length == 0)
479 return;
480 text = (char*)gtk_entry_get_text(entry);
482 /* move focus from inputbox to print potential messages that could not be
483 * printed as long as the inputbox is focused */
484 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
486 if (length > 1 && text[0] == ':') {
487 process_line((text + 1));
488 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
489 webkit_web_view_unmark_text_matches(gui->webview);
490 #ifdef ENABLE_MATCH_HIGHLITING
491 webkit_web_view_mark_text_matches(gui->webview, &text[1], FALSE, 0);
492 webkit_web_view_set_highlight_text_matches(gui->webview, TRUE);
493 #endif
494 state->count = 0;
495 #ifndef ENABLE_INCREMENTAL_SEARCH
496 a.s =& text[1];
497 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
498 search(&a);
499 #else
500 state->search_direction = forward;
501 if (state->search_handle) {
502 g_free(state->search_handle);
504 state->search_handle = g_strdup(&text[1]);
505 #endif
506 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
507 a.i = Silent;
508 a.s = g_strdup_printf("hints.fire();");
509 script(&a);
510 g_free(a.s);
511 update_state();
512 } else
513 return;
514 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
517 gboolean
518 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
519 Arg a;
520 int numval;
521 State *state = &client.state;
523 if (state->mode == ModeHints) {
524 if (event->keyval == GDK_Tab) {
525 a.i = Silent;
526 a.s = g_strdup_printf("hints.focusNextHint();");
527 script(&a);
528 g_free(a.s);
529 update_state();
530 return TRUE;
532 if (event->keyval == GDK_ISO_Left_Tab) {
533 a.i = Silent;
534 a.s = g_strdup_printf("hints.focusPreviousHint();");
535 script(&a);
536 g_free(a.s);
537 update_state();
538 return TRUE;
540 if (event->keyval == GDK_Return) {
541 a.i = Silent;
542 a.s = g_strdup_printf("hints.fire();");
543 script(&a);
544 g_free(a.s);
545 update_state();
546 return TRUE;
549 switch (event->keyval) {
550 case GDK_bracketleft:
551 case GDK_Escape:
552 if (!IS_ESCAPE(event)) break;
553 a.i = HideCompletion;
554 complete(&a);
555 a.i = ModeNormal;
556 state->commandpointer = 0;
557 return set(&a);
558 break;
559 case GDK_Tab:
560 a.i = DirectionNext;
561 return complete(&a);
562 break;
563 case GDK_Up:
564 a.i = DirectionPrev;
565 return commandhistoryfetch(&a);
566 break;
567 case GDK_Down:
568 a.i = DirectionNext;
569 return commandhistoryfetch(&a);
570 break;
571 case GDK_ISO_Left_Tab:
572 a.i = DirectionPrev;
573 return complete(&a);
574 break;
577 if (state->mode == ModeHints) {
578 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
579 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
580 (event->keyval == GDK_BackSpace)) {
581 state->count /= 10;
582 a.i = Silent;
583 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
584 script(&a);
585 g_free(a.s);
586 update_state();
587 return TRUE;
590 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
591 if ((numval >= 1 && numval <= 9) || (numval == 0 && state->count)) {
592 /* allow a zero as non-first number */
593 state->count = (state->count ? state->count * 10 : 0) + numval;
594 a.i = Silent;
595 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
596 script(&a);
597 g_free(a.s);
598 update_state();
599 return TRUE;
603 return FALSE;
606 gboolean
607 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
608 int i;
609 WebKitHitTestResult *result;
610 WebKitHitTestResultContext context;
611 State *state = &client.state;
612 if (state->mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
613 /* handle mouse click events */
614 for (i = 0; i < LENGTH(mouse); i++) {
615 if (mouse[i].mask == CLEAN(event->button.state)
616 && (mouse[i].modkey == state->current_modkey
617 || (!mouse[i].modkey && !state->current_modkey)
618 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
619 && mouse[i].button == event->button.button
620 && mouse[i].func) {
621 if (mouse[i].func(&mouse[i].arg)) {
622 state->current_modkey = state->count = 0;
623 update_state();
624 return TRUE;
628 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
629 g_object_get(result, "context", &context, NULL);
630 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
631 Arg a = { .i = ModeInsert };
632 set(&a);
633 state->manual_focus = TRUE;
635 } else if (state->mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
636 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
637 g_object_get(result, "context", &context, NULL);
638 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
639 Arg a = { .i = ModeNormal };
640 set(&a);
642 } else {
643 gchar *value = NULL, *message = NULL;
644 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
645 if (value && !strcmp(value, "[object HTMLFormElement]")) {
646 Arg a = { .i = ModeInsert, .s = NULL };
647 set(&a);
648 state->manual_focus = TRUE;
650 g_free(value);
651 g_free(message);
653 return FALSE;
656 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
657 Arg a;
658 guint16 length = gtk_entry_get_text_length(entry);
660 if (!length) {
661 a.i = HideCompletion;
662 complete(&a);
663 a.i = ModeNormal;
664 return set(&a);
666 return FALSE;
669 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
670 Arg a;
671 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
672 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
673 gboolean forward = FALSE;
675 /* Update incremental search if the user changes the search text.
677 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
678 * from the user. But if the entry is focused and the text is set
679 * through gtk_entry_set_text() in some asyncrounous operation,
680 * I would consider that a bug.
683 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
684 webkit_web_view_unmark_text_matches(client.gui.webview);
685 webkit_web_view_search_text(client.gui.webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
686 return TRUE;
687 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
688 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
689 a.i = Silent;
690 switch (text[0]) {
691 case '.':
692 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
693 break;
695 case ',':
696 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
697 break;
699 case ';':
700 a.s = NULL;
701 switch (text[1]) {
702 case 's':
703 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
704 break;
705 case 'y':
706 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
707 break;
708 case 'o':
709 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
710 break;
711 case 't': case 'w':
712 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
713 break;
714 case 'O': case 'T': case 'W':
715 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
716 break;
717 case 'i':
718 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
719 break;
720 case 'I':
721 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
722 break;
724 break;
726 client.state.count = 0;
727 if (a.s) {
728 script(&a);
729 g_free(a.s);
732 return TRUE;
733 } else if (length == 0) {
734 client.state.mode = ModeNormal;
735 a.i = Silent;
736 a.s = g_strdup("hints.clearHints();");
737 script(&a);
738 g_free(a.s);
739 client.state.count = 0;
740 update_state();
743 return FALSE;
746 /* funcs here */
748 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
749 memset(suggline, 0, 512);
750 strncpy(suggline, command, 512);
751 strncat(suggline, " ", 1);
752 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
755 GtkWidget * fill_eventbox(const char * completion_line) {
756 GtkBox * row;
757 GtkWidget *row_eventbox, *el;
758 GdkColor color;
759 char *markup, *markup_tmp;
761 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
762 row_eventbox = gtk_event_box_new();
763 gdk_color_parse(completionbgcolor[0], &color);
764 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
765 el = gtk_label_new(NULL);
766 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
767 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
768 markup_tmp, "</span>", NULL);
769 gtk_label_set_markup(GTK_LABEL(el), markup);
770 g_free(markup_tmp);
771 g_free(markup);
772 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
773 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
774 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
775 return row_eventbox;
778 gboolean
779 complete(const Arg *arg) {
780 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
781 size_t listlen, len, cmdlen;
782 int i, spacepos;
783 Listelement *elementlist = NULL, *elementpointer;
784 gboolean highlight = FALSE;
785 GtkBox *row;
786 GtkWidget *row_eventbox, *el;
787 GtkBox *_table;
788 GdkColor color;
789 static GtkWidget *table, *top_border;
790 static char *prefix;
791 static char **suggestions;
792 static GtkWidget **widgets;
793 static int n = 0, m, current = -1;
794 Gui *gui = &client.gui;
796 str = (char*)gtk_entry_get_text(GTK_ENTRY(gui->inputbox));
797 len = strlen(str);
799 /* Get the length of the list of commands for completion. We need this to
800 * malloc/realloc correctly.
802 listlen = LENGTH(commands);
804 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
805 return TRUE;
806 if (prefix) {
807 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
808 gdk_color_parse(completionbgcolor[0], &color);
809 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
810 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
811 if ((arg->i == DirectionNext && current == 0)
812 || (arg->i == DirectionPrev && current == n - 1))
813 current = -1;
814 } else {
815 free(widgets);
816 free(suggestions);
817 free(prefix);
818 gtk_widget_destroy(GTK_WIDGET(table));
819 gtk_widget_destroy(GTK_WIDGET(top_border));
820 table = NULL;
821 widgets = NULL;
822 suggestions = NULL;
823 prefix = NULL;
824 n = 0;
825 current = -1;
826 if (arg->i == HideCompletion)
827 return TRUE;
829 } else if (arg->i == HideCompletion)
830 return TRUE;
831 if (!widgets) {
832 prefix = g_strdup(str);
833 widgets = malloc(sizeof(GtkWidget*) * listlen);
834 suggestions = malloc(sizeof(char*) * listlen);
835 top_border = gtk_event_box_new();
836 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
837 gdk_color_parse(completioncolor[2], &color);
838 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
839 table = gtk_event_box_new();
840 gdk_color_parse(completionbgcolor[0], &color);
841 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
842 highlight = len > 1;
843 if (strchr(str, ' ') == NULL) {
844 /* command completion */
845 listlen = LENGTH(commands);
846 for (i = 0; i < listlen; i++) {
847 if (commands[i].cmd == NULL)
848 break;
849 cmdlen = strlen(commands[i].cmd);
850 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
851 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
852 if (highlight) {
853 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
854 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
855 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
856 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
858 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
859 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
860 row_eventbox = gtk_event_box_new();
861 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
862 el = gtk_label_new(NULL);
863 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
864 free(s);
865 gtk_label_set_markup(GTK_LABEL(el), markup);
866 g_free(markup);
867 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
868 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
869 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
870 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
871 suggestions[n] = commands[i].cmd;
872 widgets[n++] = row_eventbox;
875 } else {
876 entry = (char *)malloc(512 * sizeof(char));
877 if (entry == NULL) {
878 return FALSE;
880 memset(entry, 0, 512);
881 suggurls = malloc(sizeof(char*) * listlen);
882 if (suggurls == NULL) {
883 return FALSE;
885 spacepos = strcspn(str, " ");
886 searchfor = (str + spacepos + 1);
887 strncpy(command, (str + 1), spacepos - 1);
888 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
889 /* browser settings */
890 listlen = LENGTH(browsersettings);
891 for (i = 0; i < listlen; i++) {
892 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
893 /* match */
894 fill_suggline(suggline, command, browsersettings[i].name);
895 /* FIXME(HP): This memory is never freed */
896 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
897 strncpy(suggurls[n], suggline, 512);
898 suggestions[n] = suggurls[n];
899 row_eventbox = fill_eventbox(suggline);
900 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
901 widgets[n++] = row_eventbox;
905 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
906 /* completion on tags */
907 spacepos = strcspn(str, " ");
908 searchfor = (str + spacepos + 1);
909 elementlist = complete_list(searchfor, 1, elementlist);
910 } else {
911 /* URL completion: bookmarks */
912 elementlist = complete_list(searchfor, 0, elementlist);
913 m = count_list(elementlist);
914 if (m < MAX_LIST_SIZE) {
915 /* URL completion: history */
916 elementlist = complete_list(searchfor, 2, elementlist);
919 elementpointer = elementlist;
920 while (elementpointer != NULL) {
921 fill_suggline(suggline, command, elementpointer->element);
922 /* FIXME(HP): This memory is never freed */
923 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
924 strncpy(suggurls[n], suggline, 512);
925 suggestions[n] = suggurls[n];
926 row_eventbox = fill_eventbox(suggline);
927 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
928 widgets[n++] = row_eventbox;
929 elementpointer = elementpointer->next;
930 if (n >= MAX_LIST_SIZE)
931 break;
933 free_list(elementlist);
934 if (suggurls != NULL) {
935 free(suggurls);
936 suggurls = NULL;
938 if (entry != NULL) {
939 free(entry);
940 entry = NULL;
943 /* TA: FIXME - this needs rethinking entirely. */
945 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
946 if (widgets_temp == NULL && widgets == NULL) {
947 fprintf(stderr, "Couldn't realloc() widgets\n");
948 exit(1);
950 widgets = widgets_temp;
951 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
952 if (suggestions_temp == NULL && suggestions == NULL) {
953 fprintf(stderr, "Couldn't realloc() suggestions\n");
954 exit(1);
956 suggestions = suggestions_temp;
958 if (!n) {
959 gdk_color_parse(completionbgcolor[1], &color);
960 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
961 el = gtk_label_new(NULL);
962 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
963 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
964 gtk_label_set_markup(GTK_LABEL(el), markup);
965 g_free(markup);
966 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
968 gtk_box_pack_start(gui->box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
969 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
970 gtk_box_pack_start(gui->box, GTK_WIDGET(table), FALSE, FALSE, 0);
971 gtk_widget_show_all(GTK_WIDGET(gui->window));
972 if (!n)
973 return TRUE;
974 current = arg->i == DirectionPrev ? n - 1 : 0;
976 if (current != -1) {
977 gdk_color_parse(completionbgcolor[2], &color);
978 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
979 s = g_strconcat(":", suggestions[current], NULL);
980 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), s);
981 g_free(s);
982 } else
983 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), prefix);
984 gtk_editable_set_position(GTK_EDITABLE(gui->inputbox), -1);
985 return TRUE;
988 gboolean
989 descend(const Arg *arg) {
990 char *source = (char*)webkit_web_view_get_uri(client.gui.webview), *p = &source[0], *new;
991 int i, len;
992 client.state.count = client.state.count ? client.state.count : 1;
994 if (!source)
995 return TRUE;
996 if (arg->i == Rootdir) {
997 for (i = 0; i < 3; i++) /* get to the third slash */
998 if (!(p = strchr(++p, '/')))
999 return TRUE; /* if we cannot find it quit */
1000 } else {
1001 len = strlen(source);
1002 if (!len) /* if string is empty quit */
1003 return TRUE;
1004 p = source + len; /* start at the end */
1005 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1006 ++client.state.count;
1007 for (i = 0; i < client.state.count; i++)
1008 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1009 if (p == source) /* if we reach the first char pointer quit */
1010 return TRUE;
1011 ++p; /* since we do p-- in the while, we are pointing at
1012 the char before the slash, so +1 */
1014 len = p - source + 1; /* new length = end - start + 1 */
1015 new = malloc(len + 1);
1016 memcpy(new, source, len);
1017 new[len] = '\0';
1018 webkit_web_view_load_uri(client.gui.webview, new);
1019 free(new);
1020 return TRUE;
1023 gboolean
1024 echo(const Arg *arg) {
1025 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1027 if (index < Info || index > Error)
1028 return TRUE;
1030 if (!gtk_widget_is_focus(GTK_WIDGET(client.gui.inputbox))) {
1031 set_widget_font_and_color(client.gui.inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1032 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), !arg->s ? "" : arg->s);
1035 return TRUE;
1038 static gboolean
1039 open_inspector(const Arg * arg) {
1040 gboolean inspect_enabled;
1041 WebKitWebSettings *settings;
1043 settings = webkit_web_view_get_settings(client.gui.webview);
1044 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1045 if (inspect_enabled) {
1046 webkit_web_inspector_show(client.gui.inspector);
1047 return TRUE;
1048 } else {
1049 echo_message(Error, "Webinspector is not enabled");
1050 return FALSE;
1054 gboolean
1055 input(const Arg *arg) {
1056 int pos = 0;
1057 client.state.count = 0;
1058 const char *url;
1059 int index = Info;
1060 Arg a;
1061 GtkWidget *inputbox = client.gui.inputbox;
1063 /* if inputbox hidden, show it again */
1064 if (!gtk_widget_get_visible(inputbox))
1065 gtk_widget_set_visible(inputbox, TRUE);
1067 update_state();
1069 /* Set the colour and font back to the default, so that we don't still
1070 * maintain a red colour from a warning from an end of search indicator,
1071 * etc.
1073 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1075 /* to avoid things like :open URL :open URL2 or :open :open URL */
1076 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1077 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1078 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(client.gui.webview)))
1079 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1081 gtk_widget_grab_focus(inputbox);
1082 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1084 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1085 client.state.mode = ModeHints;
1086 a.i = Silent;
1087 switch (arg->s[0]) {
1088 case '.':
1089 a.s = g_strdup("hints.createHints('', 'f');");
1090 break;
1092 case ',':
1093 a.s = g_strdup("hints.createHints('', 'F');");
1094 break;
1096 case ';':
1097 a.s = NULL;
1098 if (arg->s[1]) {
1099 switch (arg->s[1]) {
1100 case 's':
1101 a.s = g_strdup("hints.createHints('', 's');");
1102 break;
1103 case 'y':
1104 a.s = g_strdup("hints.createHints('', 'y');");
1105 break;
1106 case 'o':
1107 a.s = g_strdup("hints.createHints('', 'f');");
1108 break;
1109 case 't': case 'w':
1110 a.s = g_strdup("hints.createHints('', 'F');");
1111 break;
1112 case 'O': case 'T': case 'W':
1113 a.s = g_strdup("hints.createHints('', 'O');");
1114 break;
1115 case 'i':
1116 a.s = g_strdup("hints.createHints('', 'i');");
1117 break;
1118 case 'I':
1119 a.s = g_strdup("hints.createHints('', 'I');");
1120 break;
1123 break;
1125 client.state.count = 0;
1126 if (a.s) {
1127 script(&a);
1128 g_free(a.s);
1132 return TRUE;
1135 gboolean
1136 navigate(const Arg *arg) {
1137 if (arg->i & NavigationForwardBack)
1138 webkit_web_view_go_back_or_forward(client.gui.webview, (arg->i == NavigationBack ? -1 : 1) * (client.state.count ? client.state.count : 1));
1139 else if (arg->i & NavigationReloadActions)
1140 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(client.gui.webview);
1141 else
1142 webkit_web_view_stop_loading(client.gui.webview);
1143 return TRUE;
1146 gboolean
1147 number(const Arg *arg) {
1148 const char *source = webkit_web_view_get_uri(client.gui.webview);
1149 char *uri, *p, *new;
1150 int number, diff = (client.state.count ? client.state.count : 1) * (arg->i == Increment ? 1 : -1);
1152 if (!source)
1153 return TRUE;
1154 uri = g_strdup(source); /* copy string */
1155 p =& uri[0];
1156 while(*p != '\0') /* goto the end of the string */
1157 ++p;
1158 --p;
1159 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1160 --p;
1161 if (*(++p) == '\0') { /* if no numbers were found abort */
1162 free(uri);
1163 return TRUE;
1165 number = atoi(p) + diff; /* apply diff on number */
1166 *p = '\0';
1167 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1168 webkit_web_view_load_uri(client.gui.webview, new);
1169 g_free(new);
1170 free(uri);
1171 return TRUE;
1174 gboolean
1175 open_arg(const Arg *arg) {
1176 char *argv[64];
1177 char *s = arg->s, *p = NULL, *new;
1178 Arg a = { .i = NavigationReload };
1179 int len;
1180 char *search_uri, *search_term;
1182 if (client.state.embed) {
1183 gchar winid[64];
1184 snprintf(winid, LENGTH(winid), "%u", (gint)client.state.embed);
1185 argv[0] = *args;
1186 argv[1] = "-e";
1187 argv[2] = winid;
1188 argv[3] = arg->s;
1189 argv[4] = NULL;
1190 } else {
1191 argv[0] = *args;
1192 argv[1] = arg->s;
1193 argv[2] = NULL;
1196 if (!arg->s)
1197 navigate(&a);
1198 else if (arg->i == TargetCurrent) {
1199 while(*s == ' ') /* strip leading whitespace */
1200 ++s;
1201 p = (s + strlen(s) - 1);
1202 while(*p == ' ') /* strip trailing whitespace */
1203 --p;
1204 *(p + 1) = '\0';
1205 len = strlen(s);
1206 new = NULL;
1207 /* check for external handlers */
1208 if (open_handler(s))
1209 return TRUE;
1210 /* check for search engines */
1211 p = strchr(s, ' ');
1212 if (p) { /* check for search engines */
1213 *p = '\0';
1214 search_uri = find_uri_for_searchengine(s);
1215 if (search_uri != NULL) {
1216 search_term = soup_uri_encode(p+1, "&");
1217 new = g_strdup_printf(search_uri, search_term);
1218 g_free(search_term);
1220 *p = ' ';
1222 if (!new) {
1223 if (len > 3 && strstr(s, "://")) { /* valid url? */
1224 p = new = g_malloc(len + 1);
1225 while(*s != '\0') { /* strip whitespaces */
1226 if (*s != ' ')
1227 *(p++) = *s;
1228 ++s;
1230 *p = '\0';
1231 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1232 new = g_malloc(sizeof("file://") + len);
1233 strcpy(new, "file://");
1234 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1235 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1236 search_uri = find_uri_for_searchengine(defaultsearch);
1237 if (search_uri != NULL) {
1238 search_term = soup_uri_encode(s, "&");
1239 new = g_strdup_printf(search_uri, search_term);
1240 g_free(search_term);
1242 } else { /* prepend "http://" */
1243 new = g_malloc(sizeof("http://") + len);
1244 strcpy(new, "http://");
1245 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1248 webkit_web_view_load_uri(client.gui.webview, new);
1249 g_free(new);
1250 } else
1251 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1252 return TRUE;
1255 gboolean
1256 open_remembered(const Arg *arg)
1258 Arg a = {arg->i, client.state.rememberedURI};
1260 if (strcmp(client.state.rememberedURI, "")) {
1261 open_arg(&a);
1263 return TRUE;
1266 gboolean
1267 yank(const Arg *arg) {
1268 const char *url, *content;
1270 if (arg->i & SourceSelection) {
1271 webkit_web_view_copy_clipboard(client.gui.webview);
1272 if (arg->i & ClipboardPrimary)
1273 content = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1274 if (!content && arg->i & ClipboardGTK)
1275 content = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1276 if (content) {
1277 echo_message(Info, "Yanked %s", content);
1278 g_free((gpointer *)content);
1280 } else {
1281 if (arg->i & SourceURL) {
1282 url = webkit_web_view_get_uri(client.gui.webview);
1283 } else {
1284 url = arg->s;
1286 if (!url)
1287 return TRUE;
1289 echo_message(Info, "Yanked %s", url);
1290 if (arg->i & ClipboardPrimary)
1291 gtk_clipboard_set_text(client.state.clipboards[0], url, -1);
1292 if (arg->i & ClipboardGTK)
1293 gtk_clipboard_set_text(client.state.clipboards[1], url, -1);
1295 return TRUE;
1298 gboolean
1299 paste(const Arg *arg) {
1300 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1302 /* If we're over a link, open it in a new target. */
1303 if (strlen(client.state.rememberedURI) > 0) {
1304 Arg new_target = { .i = TargetNew, .s = arg->s };
1305 open_arg(&new_target);
1306 return TRUE;
1309 if (arg->i & ClipboardPrimary)
1310 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1311 if (!a.s && arg->i & ClipboardGTK)
1312 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1313 if (a.s) {
1314 open_arg(&a);
1315 g_free(a.s);
1317 return TRUE;
1320 gboolean
1321 quit(const Arg *arg) {
1322 FILE *f;
1323 const char *filename;
1324 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1325 if (uri != NULL) {
1326 /* write last URL into status file for recreation with "u" */
1327 filename = g_strdup_printf("%s", client.config.config_base);
1328 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1329 f = fopen(filename, "w");
1330 g_free((gpointer *)filename);
1331 if (f != NULL) {
1332 fprintf(f, "%s", uri);
1333 fclose(f);
1336 gtk_main_quit();
1337 return TRUE;
1340 gboolean
1341 revive(const Arg *arg) {
1342 FILE *f;
1343 const char *filename;
1344 char buffer[512] = "";
1345 Arg a = { .i = TargetNew, .s = NULL };
1346 /* get the URL of the window which has been closed last */
1347 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1348 f = fopen(filename, "r");
1349 g_free((gpointer *)filename);
1350 if (f != NULL) {
1351 fgets(buffer, 512, f);
1352 fclose(f);
1354 if (strlen(buffer) > 0) {
1355 a.s = buffer;
1356 open_arg(&a);
1357 return TRUE;
1359 return FALSE;
1362 static
1363 gboolean print_frame(const Arg *arg)
1365 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1366 webkit_web_frame_print (frame);
1367 return TRUE;
1370 gboolean
1371 search(const Arg *arg) {
1372 State *state = &client.state;
1373 state->count = state->count ? state->count : 1;
1374 gboolean success, direction = arg->i & DirectionPrev;
1376 if (arg->s) {
1377 if (state->search_handle) {
1378 g_free(state->search_handle);
1380 state->search_handle = g_strdup(arg->s);
1382 if (!state->search_handle)
1383 return TRUE;
1384 if (arg->i & DirectionAbsolute)
1385 state->search_direction = direction;
1386 else
1387 direction ^= state->search_direction;
1388 do {
1389 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, FALSE);
1390 if (!success) {
1391 if (arg->i & Wrapping) {
1392 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, TRUE);
1393 if (success) {
1394 echo_message(Warning, "search hit %s, continuing at %s",
1395 direction ? "BOTTOM" : "TOP",
1396 direction ? "TOP" : "BOTTOM");
1397 } else
1398 break;
1399 } else
1400 break;
1402 } while(--state->count);
1403 if (!success) {
1404 echo_message(Error, "Pattern not found: %s", state->search_handle);
1406 return TRUE;
1409 gboolean
1410 set(const Arg *arg) {
1411 switch (arg->i) {
1412 case ModeNormal:
1413 if (client.state.search_handle) {
1414 g_free(client.state.search_handle);
1415 client.state.search_handle = NULL;
1416 webkit_web_view_unmark_text_matches(client.gui.webview);
1418 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), "");
1419 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1420 break;
1421 case ModePassThrough:
1422 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1423 break;
1424 case ModeSendKey:
1425 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1426 break;
1427 case ModeInsert: /* should not be called manually but automatically */
1428 echo_message(Info | NoAutoHide, "-- INSERT --");
1429 break;
1430 default:
1431 return TRUE;
1433 client.state.mode = arg->i;
1434 return TRUE;
1437 gchar*
1438 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1439 JSStringRef string_ref;
1440 gchar *string;
1441 size_t length;
1443 string_ref = JSValueToStringCopy(context, ref, NULL);
1444 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1445 string = g_new(gchar, length);
1446 JSStringGetUTF8CString(string_ref, string, length);
1447 JSStringRelease(string_ref);
1448 return string;
1451 void
1452 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1453 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1454 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1455 JSStringRef str;
1456 JSValueRef val, exception;
1458 str = JSStringCreateWithUTF8CString(script);
1459 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1460 JSStringRelease(str);
1461 if (!val)
1462 *message = jsapi_ref_to_string(context, exception);
1463 else
1464 *value = jsapi_ref_to_string(context, val);
1467 gboolean
1468 quickmark(const Arg *a) {
1469 int i, b;
1470 b = atoi(a->s);
1471 char *fn = g_strdup_printf(QUICKMARK_FILE);
1472 FILE *fp;
1473 fp = fopen(fn, "r");
1474 g_free(fn);
1475 fn = NULL;
1476 char buf[100];
1478 if (fp != NULL && b < 10) {
1479 for( i=0; i < b; ++i ) {
1480 if (feof(fp)) {
1481 break;
1483 fgets(buf, 100, fp);
1485 char *ptr = strrchr(buf, '\n');
1486 *ptr = '\0';
1487 if (strlen(buf)) {
1488 Arg x = { .s = buf };
1489 return open_arg(&x);
1490 } else {
1491 echo_message(Error, "Quickmark %d not defined", b);
1492 return false;
1494 } else { return false; }
1497 gboolean
1498 script(const Arg *arg) {
1499 gchar *value = NULL, *message = NULL;
1500 char text[BUF_SIZE] = "";
1501 Arg a;
1502 WebKitNetworkRequest *request;
1503 WebKitDownload *download;
1505 if (!arg->s) {
1506 set_error("Missing argument.");
1507 return FALSE;
1509 jsapi_evaluate_script(arg->s, &value, &message);
1510 if (message) {
1511 set_error(message);
1512 g_free(value);
1513 g_free(message);
1514 return FALSE;
1516 g_free(message);
1517 if (arg->i != Silent && value) {
1518 echo_message(arg->i, value);
1520 /* switch mode according to scripts return value */
1521 if (value) {
1522 if (strncmp(value, "done;", 5) == 0) {
1523 a.i = ModeNormal;
1524 set(&a);
1525 } else if (strncmp(value, "insert;", 7) == 0) {
1526 a.i = ModeInsert;
1527 set(&a);
1528 client.state.manual_focus = TRUE;
1529 } else if (strncmp(value, "save;", 5) == 0) {
1530 /* forced download */
1531 a.i = ModeNormal;
1532 set(&a);
1533 request = webkit_network_request_new((value + 5));
1534 download = webkit_download_new(request);
1535 webview_download_cb(client.gui.webview, download, (gpointer *)NULL);
1536 } else if (strncmp(value, "yank;", 5) == 0) {
1537 /* yank link URL to clipboard */
1538 a.i = ModeNormal;
1539 set(&a);
1540 a.i = ClipboardPrimary | ClipboardGTK;
1541 a.s = (value + 5);
1542 yank(&a);
1543 } else if (strncmp(value, "colon;", 6) == 0) {
1544 /* use link URL for colon command */
1545 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(client.gui.inputbox)), 1023);
1546 a.i = ModeNormal;
1547 set(&a);
1548 switch (text[1]) {
1549 case 'O':
1550 a.s = g_strconcat(":open ", (value + 6), NULL);
1551 break;
1552 case 'T': case 'W':
1553 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1554 break;
1556 if (a.s) {
1557 input(&a);
1558 g_free(a.s);
1560 } else if (strncmp(value, "error;", 6) == 0) {
1561 a.i = Error;
1562 set(&a);
1565 g_free(value);
1566 return TRUE;
1569 gboolean
1570 scroll(const Arg *arg) {
1571 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? client.gui.adjust_h : client.gui.adjust_v;
1572 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1573 float val = gtk_adjustment_get_value(adjust) / max * 100;
1574 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1575 unsigned int count = client.state.count;
1577 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1578 if (arg->i & ScrollMove)
1579 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1580 direction * /* direction */
1581 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1582 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1583 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1584 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1585 else
1586 gtk_adjustment_set_value(adjust,
1587 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1588 update_state();
1590 return TRUE;
1593 gboolean
1594 zoom(const Arg *arg) {
1595 unsigned int count = client.state.count;
1597 webkit_web_view_set_full_content_zoom(client.gui.webview, (arg->i & ZoomFullContent) > 0);
1598 webkit_web_view_set_zoom_level(client.gui.webview, (arg->i & ZoomOut) ?
1599 webkit_web_view_get_zoom_level(client.gui.webview) +
1600 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * client.config.zoomstep) :
1601 (count ? (float)count / 100.0 : 1.0));
1602 return TRUE;
1605 gboolean
1606 fake_key_event(const Arg *a) {
1607 if(!client.state.embed) {
1608 return FALSE;
1610 Display *xdpy;
1611 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1612 echo_message(Error, "Couldn't find the XDisplay.");
1613 return FALSE;
1616 XKeyEvent xk;
1617 xk.display = xdpy;
1618 xk.subwindow = None;
1619 xk.time = CurrentTime;
1620 xk.same_screen = True;
1621 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1622 xk.window = client.state.embed;
1623 xk.state = a->i;
1625 if( ! a->s ) {
1626 echo_message(Error, "Zero pointer as argument! Check your config.h");
1627 return FALSE;
1630 KeySym keysym;
1631 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1632 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1633 return FALSE;
1636 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1637 echo_message(Error, "Couldn't translate keysym to keycode");
1638 return FALSE;
1641 xk.type = KeyPress;
1642 if( !XSendEvent(xdpy, client.state.embed, True, KeyPressMask, (XEvent *)&xk) ) {
1643 echo_message(Error, "XSendEvent failed");
1644 return FALSE;
1646 XFlush(xdpy);
1648 return TRUE;
1651 gboolean
1652 commandhistoryfetch(const Arg *arg) {
1653 State *state = &client.state;
1654 const int length = g_list_length(client.state.commandhistory);
1655 gchar *input_message = NULL;
1657 if (length > 0) {
1658 if (arg->i == DirectionPrev) {
1659 state->commandpointer = (length + state->commandpointer - 1) % length;
1660 } else {
1661 state->commandpointer = (length + state->commandpointer + 1) % length;
1664 const char* command = (char *)g_list_nth_data(state->commandhistory, state->commandpointer);
1665 input_message = g_strconcat(":", command, NULL);
1666 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), input_message);
1667 g_free(input_message);
1668 gtk_editable_set_position(GTK_EDITABLE(client.gui.inputbox), -1);
1669 return TRUE;
1672 return FALSE;
1675 gboolean
1676 bookmark(const Arg *arg) {
1677 FILE *f;
1678 const char *filename;
1679 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1680 const char *title = webkit_web_view_get_title(client.gui.webview);
1681 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1682 f = fopen(filename, "a");
1683 g_free((gpointer *)filename);
1684 if (uri == NULL || strlen(uri) == 0) {
1685 set_error("No URI found to bookmark.");
1686 return FALSE;
1688 if (f != NULL) {
1689 fprintf(f, "%s", uri);
1690 if (title != NULL) {
1691 fprintf(f, "%s", " ");
1692 fprintf(f, "%s", title);
1694 if (arg->s && strlen(arg->s)) {
1695 build_taglist(arg, f);
1697 fprintf(f, "%s", "\n");
1698 fclose(f);
1699 echo_message(Info, "Bookmark saved");
1700 return TRUE;
1701 } else {
1702 set_error("Bookmarks file not found.");
1703 return FALSE;
1707 gboolean
1708 history() {
1709 FILE *f;
1710 const char *filename;
1711 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1712 const char *title = webkit_web_view_get_title(client.gui.webview);
1713 char *entry, buffer[512], *new;
1714 int n, i = 0;
1715 gboolean finished = FALSE;
1716 if (uri != NULL) {
1717 if (title != NULL) {
1718 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1719 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1720 } else {
1721 entry = malloc((strlen(uri) + 1) * sizeof(char));
1722 memset(entry, 0, strlen(uri) + 1);
1724 if (entry != NULL) {
1725 strncpy(entry, uri, strlen(uri));
1726 if (title != NULL) {
1727 strncat(entry, " ", 1);
1728 strncat(entry, title, strlen(title));
1730 n = strlen(entry);
1731 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1732 f = fopen(filename, "r");
1733 if (f != NULL) {
1734 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1735 if (new != NULL) {
1736 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1737 /* newest entries go on top */
1738 strncpy(new, entry, strlen(entry));
1739 strncat(new, "\n", 1);
1740 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1741 while (finished != TRUE) {
1742 if ((char *)NULL == fgets(buffer, 512, f)) {
1743 /* check if end of file was reached / error occured */
1744 if (!feof(f)) {
1745 break;
1747 /* end of file reached */
1748 finished = TRUE;
1749 continue;
1751 /* compare line (-1 because of newline character) */
1752 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1753 /* if the URI is already in history; we put it on top and skip it here */
1754 strncat(new, buffer, 512);
1755 i++;
1757 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1758 break;
1761 fclose(f);
1763 f = fopen(filename, "w");
1764 g_free((gpointer *)filename);
1765 if (f != NULL) {
1766 fprintf(f, "%s", new);
1767 fclose(f);
1769 if (new != NULL) {
1770 free(new);
1771 new = NULL;
1775 if (entry != NULL) {
1776 free(entry);
1777 entry = NULL;
1780 return TRUE;
1783 static gboolean
1784 view_source(const Arg * arg) {
1785 gboolean current_mode = webkit_web_view_get_view_source_mode(client.gui.webview);
1786 webkit_web_view_set_view_source_mode(client.gui.webview, !current_mode);
1787 webkit_web_view_reload(client.gui.webview);
1788 return TRUE;
1791 /* open an external editor defined by the protocol handler for
1792 vimprobableedit on a text box or similar */
1793 static gboolean
1794 open_editor(const Arg *arg) {
1795 char *text = NULL;
1796 gboolean success;
1797 GPid child_pid;
1798 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1799 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1800 temp_dir);
1801 int temp_file_handle = -1;
1803 /* check if active element is suitable for text editing */
1804 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1805 if (value == NULL) {
1806 g_free(message);
1807 return FALSE;
1809 tag = g_strdup(value);
1810 if (strcmp(tag, "INPUT") == 0) {
1811 /* extra check: type == text */
1812 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1813 if (strcmp(value, "text") != 0) {
1814 g_free(value);
1815 g_free(message);
1816 return FALSE;
1818 g_free(value);
1819 g_free(message);
1820 } else if (strcmp(tag, "TEXTAREA") != 0) {
1821 g_free(value);
1822 g_free(message);
1823 return FALSE;
1825 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1826 text = g_strdup(value);
1827 if (text == NULL) {
1828 g_free(value);
1829 g_free(message);
1830 return FALSE;
1833 /* write text into temporary file */
1834 temp_file_handle = mkstemp(temp_file_name);
1835 if (temp_file_handle == -1) {
1836 message = g_strdup_printf("Could not create temporary file: %s",
1837 strerror(errno));
1838 echo_message(Error, message);
1839 g_free(value);
1840 g_free(message);
1841 g_free(text);
1842 return FALSE;
1844 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1845 message = g_strdup_printf("Short write to temporary file: %s",
1846 strerror(errno));
1847 echo_message(Error, message);
1848 g_free(value);
1849 g_free(message);
1850 g_free(text);
1851 return FALSE;
1853 close(temp_file_handle);
1854 g_free(text);
1856 /* spawn editor */
1857 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1858 success = open_handler_pid(edit_url, &child_pid);
1859 g_free(edit_url);
1860 if (!success) {
1861 echo_message(Error, "External editor open failed (no handler for"
1862 " vimprobableedit protocol?)");
1863 unlink(temp_file_name);
1864 g_free(value);
1865 g_free(message);
1866 return FALSE;
1869 /* mark the active text box as "under processing" */
1870 jsapi_evaluate_script(
1871 "document.activeElement.disabled = true;"
1872 "document.activeElement.originalBackground = "
1873 " document.activeElement.style.background;"
1874 "document.activeElement.style.background = '#aaaaaa';"
1875 ,&value, &message);
1877 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1879 /* temp_file_name is freed in _resume_from_editor */
1880 g_free(value);
1881 g_free(message);
1882 g_free(tag);
1883 return TRUE;
1887 /* pick up from where open_editor left the work to the glib event loop.
1889 This is called when the external editor exits.
1891 The data argument points to allocated memory containing the temporary file
1892 name. */
1893 void
1894 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1895 FILE *fp;
1896 GString *set_value_js = g_string_new(
1897 "document.activeElement.value = \"");
1898 g_spawn_close_pid(child_pid);
1899 gchar *value = NULL, *message = NULL;
1900 gchar *temp_file_name = data;
1901 gchar buffer[BUF_SIZE] = "";
1902 gchar *buf_ptr = buffer;
1903 int char_read;
1905 jsapi_evaluate_script(
1906 "document.activeElement.disabled = true;"
1907 "document.activeElement.style.background = '#aaaaaa';"
1908 ,&value, &message);
1909 g_free(value);
1910 g_free(message);
1912 if (child_status) {
1913 echo_message(Error, "External editor returned with non-zero status,"
1914 " discarding edits.");
1915 goto error_exit;
1918 /* re-read the new contents of the file and put it into the HTML element */
1919 if (!access(temp_file_name, R_OK) == 0) {
1920 message = g_strdup_printf("Could not access temporary file: %s",
1921 strerror(errno));
1922 goto error_exit;
1924 fp = fopen(temp_file_name, "r");
1925 if (fp == NULL) {
1926 /* this would be too weird to even emit an error message */
1927 goto error_exit;
1929 jsapi_evaluate_script("document.activeElement.value = '';",
1930 &value, &message);
1931 g_free(value);
1932 g_free(message);
1934 while (EOF != (char_read = fgetc(fp))) {
1935 if (char_read == '\n') {
1936 *buf_ptr++ = '\\';
1937 *buf_ptr++ = 'n';
1938 } else if (char_read == '"') {
1939 *buf_ptr++ = '\\';
1940 *buf_ptr++ = '"';
1941 } else {
1942 *buf_ptr++ = char_read;
1944 /* ship out as the buffer when space gets tight. This has
1945 fuzz to save on thinking, plus we have enough space for the
1946 trailing "; in any case. */
1947 if (buf_ptr-buffer>=BUF_SIZE-10) {
1948 *buf_ptr = 0;
1949 g_string_append(set_value_js, buffer);
1950 buf_ptr = buffer;
1953 *buf_ptr++ = '"';
1954 *buf_ptr++ = ';';
1955 *buf_ptr = 0;
1956 g_string_append(set_value_js, buffer);
1957 fclose(fp);
1959 jsapi_evaluate_script(set_value_js->str, &value, &message);
1960 g_free(value);
1961 g_free(message);
1963 /* Fall through, error and normal exit are identical */
1964 error_exit:
1965 jsapi_evaluate_script(
1966 "document.activeElement.disabled = false;"
1967 "document.activeElement.style.background ="
1968 " document.activeElement.originalBackground;"
1969 ,&value, &message);
1971 g_string_free(set_value_js, TRUE);
1972 unlink(temp_file_name);
1973 g_free(temp_file_name);
1974 g_free(value);
1975 g_free(message);
1978 static gboolean
1979 focus_input(const Arg *arg) {
1980 static Arg a;
1982 a.s = g_strdup("hints.focusInput();");
1983 a.i = Silent;
1984 script(&a);
1985 g_free(a.s);
1986 update_state();
1987 client.state.manual_focus = TRUE;
1988 return TRUE;
1991 static void
1992 clear_focus(void) {
1993 static Arg a;
1995 a.s = g_strdup("hints.clearFocus();");
1996 a.i = Silent;
1997 script(&a);
1998 g_free(a.s);
1999 a.i = ModeNormal;
2000 a.s = NULL;
2001 set(&a);
2004 static gboolean
2005 browser_settings(const Arg *arg) {
2006 char line[255];
2007 if (!arg->s) {
2008 set_error("Missing argument.");
2009 return FALSE;
2011 strncpy(line, arg->s, 254);
2012 if (process_set_line(line))
2013 return TRUE;
2014 else {
2015 set_error("Invalid setting.");
2016 return FALSE;
2020 char *
2021 search_word(int whichword) {
2022 int k = 0;
2023 static char word[240];
2024 char *c = my_pair.line;
2026 while (isspace(*c) && *c)
2027 c++;
2029 switch (whichword) {
2030 case 0:
2031 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2032 word[k++] = *c;
2033 c++;
2035 word[k] = '\0';
2036 strncpy(my_pair.what, word, 20);
2037 break;
2038 case 1:
2039 while (*c && k < 240) {
2040 word[k++] = *c;
2041 c++;
2043 word[k] = '\0';
2044 strncpy(my_pair.value, word, 240);
2045 break;
2048 return c;
2051 static gboolean
2052 process_set_line(char *line) {
2053 char *c;
2054 int listlen, i;
2055 gboolean boolval;
2056 WebKitWebSettings *settings;
2058 settings = webkit_web_view_get_settings(client.gui.webview);
2059 my_pair.line = line;
2060 c = search_word(0);
2061 if (!strlen(my_pair.what))
2062 return FALSE;
2064 while (isspace(*c) && *c)
2065 c++;
2067 if (*c == ':' || *c == '=')
2068 c++;
2070 my_pair.line = c;
2071 c = search_word(1);
2073 listlen = LENGTH(browsersettings);
2074 for (i = 0; i < listlen; i++) {
2075 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2076 /* mandatory argument not provided */
2077 if (strlen(my_pair.value) == 0)
2078 return FALSE;
2079 /* process qmark? */
2080 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2081 return (process_save_qmark(my_pair.value, client.gui.webview));
2083 /* interpret boolean values */
2084 if (browsersettings[i].boolval) {
2085 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) {
2086 boolval = TRUE;
2087 } 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) {
2088 boolval = FALSE;
2089 } else {
2090 return FALSE;
2092 } else if (browsersettings[i].colourval) {
2093 /* interpret as hexadecimal colour */
2094 if (!parse_colour(my_pair.value)) {
2095 return FALSE;
2098 if (browsersettings[i].var != NULL) {
2099 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2100 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2101 /* in this case, \0 will not have been copied */
2102 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2103 /* in case this string is also used for a webkit setting, make sure it's consistent */
2104 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2105 echo_message(Info, "String too long; automatically truncated!");
2108 if (strlen(browsersettings[i].webkit) > 0) {
2109 /* activate appropriate webkit setting */
2110 if (browsersettings[i].boolval) {
2111 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2112 } else if (browsersettings[i].intval) {
2113 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2114 } else {
2115 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2117 webkit_web_view_set_settings(client.gui.webview, settings);
2120 if (strlen(my_pair.what) == 14) {
2121 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2122 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2123 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2124 complete_case_sensitive = boolval;
2126 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2127 toggle_proxy(boolval);
2128 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2129 toggle_scrollbars(boolval);
2130 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2131 gtk_widget_set_visible(GTK_WIDGET(client.gui.statusbar), boolval);
2132 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2133 gtk_widget_set_visible(client.gui.inputbox, boolval);
2134 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2135 escape_input_on_load = boolval;
2138 /* SSL certificate checking */
2139 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2140 if (boolval) {
2141 strict_ssl = TRUE;
2142 g_object_set(G_OBJECT(client.net.session), "ssl-strict", TRUE, NULL);
2143 } else {
2144 strict_ssl = FALSE;
2145 g_object_set(G_OBJECT(client.net.session), "ssl-strict", FALSE, NULL);
2148 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2149 g_object_set(G_OBJECT(client.net.session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2151 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2152 set_default_winsize(my_pair.value);
2155 /* reload page? */
2156 if (browsersettings[i].reload)
2157 webkit_web_view_reload(client.gui.webview);
2158 return TRUE;
2161 return FALSE;
2164 gboolean
2165 process_line(char *line) {
2166 char *c = line, *command_hist;
2167 int i;
2168 size_t len, length = strlen(line);
2169 gboolean found = FALSE, success = FALSE;
2170 Arg a;
2171 GList *l;
2173 while (isspace(*c))
2174 c++;
2175 /* Ignore blank lines. */
2176 if (c[0] == '\0')
2177 return TRUE;
2179 command_hist = g_strdup(c);
2181 /* check for colon command aliases first */
2182 for (l = client.config.colon_aliases; l; l = g_list_next(l)) {
2183 Alias *alias = (Alias *)l->data;
2184 if (length == strlen(alias->alias) && strncmp(alias->alias, line, length) == 0) {
2185 /* reroute to target command */
2186 c = alias->target;
2187 length = strlen(alias->target);
2188 break;
2192 /* check standard commands */
2193 for (i = 0; i < LENGTH(commands); i++) {
2194 if (commands[i].cmd == NULL)
2195 break;
2196 len = strlen(commands[i].cmd);
2197 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2198 found = TRUE;
2199 a.i = commands[i].arg.i;
2200 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2201 success = commands[i].func(&a);
2202 g_free(a.s);
2203 break;
2207 save_command_history(command_hist);
2208 g_free(command_hist);
2210 if (!found) {
2211 echo_message(Error, "Not a browser command: %s", c);
2212 } else if (!success) {
2213 if (client.state.error_msg != NULL) {
2214 echo_message(Error, client.state.error_msg);
2215 g_free(client.state.error_msg);
2216 client.state.error_msg = NULL;
2217 } else {
2218 echo_message(Error, "Unknown error. Please file a bug report!");
2221 return success;
2224 static gboolean
2225 search_tag(const Arg * a) {
2226 FILE *f;
2227 const char *filename;
2228 const char *tag = a->s;
2229 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2230 int t, i, intag, k;
2232 if (!tag) {
2233 /* The user must give us something to load up. */
2234 set_error("Bookmark tag required with this option.");
2235 return FALSE;
2238 if (strlen(tag) > MAXTAGSIZE) {
2239 set_error("Tag too long.");
2240 return FALSE;
2243 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2244 f = fopen(filename, "r");
2245 g_free((gpointer *)filename);
2246 if (f == NULL) {
2247 set_error("Couldn't open bookmarks file.");
2248 return FALSE;
2250 while (fgets(s, BUFFERSIZE-1, f)) {
2251 intag = 0;
2252 t = strlen(s) - 1;
2253 while (isspace(s[t]))
2254 t--;
2255 if (s[t] != ']') continue;
2256 while (t > 0) {
2257 if (s[t] == ']') {
2258 if (!intag)
2259 intag = t;
2260 else
2261 intag = 0;
2262 } else {
2263 if (s[t] == '[') {
2264 if (intag) {
2265 i = 0;
2266 k = t + 1;
2267 while (k < intag)
2268 foundtag[i++] = s[k++];
2269 foundtag[i] = '\0';
2270 /* foundtag now contains the tag */
2271 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2272 i = 0;
2273 while (isspace(s[i])) i++;
2274 k = 0;
2275 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2276 url[k] = '\0';
2277 Arg x = { .i = TargetNew, .s = url };
2278 open_arg(&x);
2281 intag = 0;
2284 t--;
2287 return TRUE;
2290 void
2291 toggle_proxy(gboolean onoff) {
2292 SoupURI *proxy_uri;
2293 char *filename, *new;
2295 if (onoff == FALSE) {
2296 g_object_set(client.net.session, "proxy-uri", NULL, NULL);
2297 } else {
2298 filename = (char *)g_getenv("http_proxy");
2300 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2301 * defined.
2303 if (filename == NULL)
2304 filename = (char *)g_getenv("HTTP_PROXY");
2306 if (filename != NULL && 0 < strlen(filename)) {
2307 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2308 proxy_uri = soup_uri_new(new);
2310 g_object_set(client.net.session, "proxy-uri", proxy_uri, NULL);
2312 soup_uri_free(proxy_uri);
2313 g_free(new);
2318 void
2319 toggle_scrollbars(gboolean onoff) {
2320 Gui *gui = &client.gui;
2321 if (onoff == TRUE) {
2322 gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2323 gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2324 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2325 } else {
2326 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2327 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2328 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2330 gtk_widget_set_scroll_adjustments (GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2332 return;
2335 void set_default_winsize(const char * const size) {
2336 char *p;
2337 int x = 640, y = 480;
2339 x = strtol(size, &p, 10);
2340 if (errno == ERANGE || x <= 0) {
2341 x = 640;
2342 goto out;
2345 if (p == size || strlen(size) == p - size)
2346 goto out;
2348 y = strtol(p + 1, NULL, 10);
2349 if (errno == ERANGE || y <= 0)
2350 y = 480;
2352 out:
2353 gtk_window_resize(GTK_WINDOW(client.gui.window), x, y);
2356 void
2357 update_url(const char *uri) {
2358 Gui *gui = &client.gui;
2359 gboolean ssl = g_str_has_prefix(uri, "https://");
2360 GdkColor color;
2361 WebKitWebFrame *frame;
2362 WebKitWebDataSource *src;
2363 WebKitNetworkRequest *request;
2364 SoupMessage *msg;
2365 gboolean ssl_ok;
2366 char *sslactivecolor;
2367 gchar *markup;
2368 #ifdef ENABLE_HISTORY_INDICATOR
2369 char before[] = " [";
2370 char after[] = "]";
2371 gboolean back = webkit_web_view_can_go_back(gui->webview);
2372 gboolean fwd = webkit_web_view_can_go_forward(gui->webview);
2374 if (!back && !fwd)
2375 before[0] = after[0] = '\0';
2376 #endif
2377 markup = g_markup_printf_escaped(
2378 #ifdef ENABLE_HISTORY_INDICATOR
2379 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2380 before, back ? "+" : "", fwd ? "-" : "", after
2381 #else
2382 "<span font=\"%s\">%s</span>", statusfont, uri
2383 #endif
2385 gtk_label_set_markup(GTK_LABEL(gui->status_url), markup);
2386 g_free(markup);
2387 if (ssl) {
2388 frame = webkit_web_view_get_main_frame(gui->webview);
2389 src = webkit_web_frame_get_data_source(frame);
2390 request = webkit_web_data_source_get_request(src);
2391 msg = webkit_network_request_get_message(request);
2392 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2393 if (ssl_ok)
2394 sslactivecolor = sslbgcolor;
2395 else
2396 sslactivecolor = sslinvalidbgcolor;
2398 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2399 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &color);
2400 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2401 gtk_widget_modify_fg(GTK_WIDGET(gui->status_url), GTK_STATE_NORMAL, &color);
2402 gtk_widget_modify_fg(GTK_WIDGET(gui->status_state), GTK_STATE_NORMAL, &color);
2405 void
2406 update_state() {
2407 State* state = &client.state;
2408 char *markup;
2409 int download_count = g_list_length(state->activeDownloads);
2410 GString *status = g_string_new("");
2412 /* construct the status line */
2414 /* count, modkey and input buffer */
2415 g_string_append_printf(status, "%.0d", state->count);
2416 if (state->current_modkey) g_string_append_c(status, state->current_modkey);
2418 /* the number of active downloads */
2419 if (state->activeDownloads) {
2420 g_string_append_printf(status, " %d active %s", download_count,
2421 (download_count == 1) ? "download" : "downloads");
2424 #ifdef ENABLE_WGET_PROGRESS_BAR
2425 /* the progressbar */
2427 int progress = -1;
2428 char progressbar[progressbartick + 1];
2430 if (state->activeDownloads) {
2431 progress = 0;
2432 GList *ptr;
2434 for (ptr = state->activeDownloads; ptr; ptr = g_list_next(ptr)) {
2435 progress += 100 * webkit_download_get_progress(ptr->data);
2438 progress /= download_count;
2440 } else if (webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FINISHED
2441 && webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FAILED) {
2443 progress = webkit_web_view_get_progress(client.gui.webview) * 100;
2446 if (progress >= 0) {
2447 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2448 g_string_append_printf(status, " %c%s%c",
2449 progressborderleft, progressbar, progressborderright);
2452 #endif
2454 /* and the current scroll position */
2456 int max = gtk_adjustment_get_upper(client.gui.adjust_v) - gtk_adjustment_get_page_size(client.gui.adjust_v);
2457 int val = (int)(gtk_adjustment_get_value(client.gui.adjust_v) / max * 100);
2459 if (max == 0)
2460 g_string_append(status, " All");
2461 else if (val == 0)
2462 g_string_append(status, " Top");
2463 else if (val == 100)
2464 g_string_append(status, " Bot");
2465 else
2466 g_string_append_printf(status, " %d%%", val);
2470 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2471 gtk_label_set_markup(GTK_LABEL(client.gui.status_state), markup);
2472 g_free(markup);
2474 g_string_free(status, TRUE);
2477 static void
2478 setup_client(void) {
2479 State *state = &client.state;
2480 Config *config = &client.config;
2482 state->mode = ModeNormal;
2483 state->count = 0;
2484 state->rememberedURI[0] = '\0';
2485 state->manual_focus = FALSE;
2486 state->commandhistory = NULL;
2487 state->commandpointer = 0;
2489 config->colon_aliases = NULL;
2490 config->cookie_timeout = 4800;
2493 void
2494 setup_modkeys() {
2495 unsigned int i;
2496 client.config.modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2497 char *ptr = client.config.modkeys;
2499 for (i = 0; i < LENGTH(keys); i++)
2500 if (keys[i].modkey && !strchr(client.config.modkeys, keys[i].modkey))
2501 *(ptr++) = keys[i].modkey;
2502 client.config.modkeys = realloc(client.config.modkeys, &ptr[0] - &client.config.modkeys[0] + 1);
2505 void
2506 setup_gui() {
2507 Gui *gui = &client.gui;
2508 State *state = &client.state;
2510 gui->scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2511 gui->scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2512 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2513 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2514 if (client.state.embed) {
2515 gui->window = GTK_WINDOW(gtk_plug_new(client.state.embed));
2516 } else {
2517 gui->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2518 gtk_window_set_wmclass(GTK_WINDOW(gui->window), "vimprobable2", "Vimprobable2");
2520 gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
2521 gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2522 gui->inputbox = gtk_entry_new();
2523 gui->webview = (WebKitWebView*)webkit_web_view_new();
2524 gui->statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2525 gui->eventbox = gtk_event_box_new();
2526 gui->status_url = gtk_label_new(NULL);
2527 gui->status_state = gtk_label_new(NULL);
2528 GdkColor bg;
2529 PangoFontDescription *font;
2530 GdkGeometry hints = { 1, 1 };
2531 gui->inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gui->webview));
2533 state->clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2534 state->clipboards[1] = gtk_clipboard_get(GDK_NONE);
2535 setup_settings();
2536 gdk_color_parse(statusbgcolor, &bg);
2537 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
2538 gtk_widget_set_name(GTK_WIDGET(gui->window), "Vimprobable2");
2539 gtk_window_set_geometry_hints(gui->window, NULL, &hints, GDK_HINT_MIN_SIZE);
2541 state->keymap = gdk_keymap_get_default();
2543 #ifdef DISABLE_SCROLLBAR
2544 gui->viewport = gtk_scrolled_window_new(NULL, NULL);
2545 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2546 #else
2547 /* Ensure we still see scrollbars. */
2548 gui->viewport = gtk_scrolled_window_new(gui->adjust_h, gui->adjust_v);
2549 #endif
2551 setup_signals();
2552 gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
2554 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2555 * titlebars, we can still scroll.
2557 gtk_widget_set_scroll_adjustments(GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2559 font = pango_font_description_from_string(urlboxfont[0]);
2560 gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
2561 pango_font_description_free(font);
2562 gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
2563 gtk_misc_set_alignment(GTK_MISC(gui->status_url), 0.0, 0.0);
2564 gtk_misc_set_alignment(GTK_MISC(gui->status_state), 1.0, 0.0);
2565 gtk_box_pack_start(gui->statusbar, gui->status_url, TRUE, TRUE, 2);
2566 gtk_box_pack_start(gui->statusbar, gui->status_state, FALSE, FALSE, 2);
2567 gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar));
2568 gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
2569 gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
2570 gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
2571 gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
2572 gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->box));
2573 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
2574 gtk_widget_show_all(GTK_WIDGET(gui->window));
2575 set_widget_font_and_color(gui->inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2576 g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2579 void
2580 setup_settings() {
2581 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2582 char *filename, *file_url;
2584 client.net.session = webkit_get_default_session();
2585 g_object_set(G_OBJECT(client.net.session), "ssl-ca-file", ca_bundle, NULL);
2586 g_object_set(G_OBJECT(client.net.session), "ssl-strict", strict_ssl, NULL);
2587 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2588 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2589 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2590 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2591 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2592 filename = g_strdup_printf(USER_STYLESHEET);
2593 file_url = g_strdup_printf("file://%s", filename);
2594 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2595 g_free(file_url);
2596 g_free(filename);
2597 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2598 g_object_get(G_OBJECT(settings), "zoom-step", &client.config.zoomstep, NULL);
2599 webkit_web_view_set_settings(client.gui.webview, settings);
2601 /* proxy */
2602 toggle_proxy(use_proxy);
2605 void
2606 setup_signals() {
2607 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
2608 #ifdef ENABLE_COOKIE_SUPPORT
2609 /* Headers. */
2610 g_signal_connect_after(G_OBJECT(client.net.session), "request-started", G_CALLBACK(new_generic_request), NULL);
2611 #endif
2612 /* Accept-language header */
2613 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2614 /* window */
2615 g_object_connect(G_OBJECT(client.gui.window),
2616 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2617 NULL);
2618 /* frame */
2619 g_signal_connect(G_OBJECT(frame),
2620 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2621 /* webview */
2622 g_object_connect(G_OBJECT(client.gui.webview),
2623 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2624 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2625 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2626 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2627 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2628 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2629 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2630 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2631 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2632 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2633 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2634 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2635 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2636 NULL);
2637 /* webview adjustment */
2638 g_object_connect(G_OBJECT(client.gui.adjust_v),
2639 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2640 NULL);
2641 /* inputbox */
2642 g_object_connect(G_OBJECT(client.gui.inputbox),
2643 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2644 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2645 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2646 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2647 NULL);
2648 /* inspector */
2649 g_signal_connect(G_OBJECT(client.gui.inspector),
2650 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2653 #ifdef ENABLE_USER_SCRIPTFILE
2654 static void
2655 scripts_run_user_file() {
2656 gchar *js = NULL, *user_scriptfile = NULL;
2657 GError *error = NULL;
2659 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2661 /* run the users script file */
2662 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2663 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2665 gchar *value = NULL, *message = NULL;
2667 jsapi_evaluate_script(js, &value, &message);
2668 g_free(js);
2669 if (message) {
2670 fprintf(stderr, "%s", message);
2672 g_free(value);
2673 g_free(message);
2674 } else {
2675 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2678 g_free(user_scriptfile);
2680 #endif
2682 #ifdef ENABLE_COOKIE_SUPPORT
2683 void
2684 setup_cookies()
2686 Network *net = &client.net;
2687 if (net->file_cookie_jar)
2688 g_object_unref(net->file_cookie_jar);
2690 if (net->session_cookie_jar)
2691 g_object_unref(net->session_cookie_jar);
2693 net->session_cookie_jar = soup_cookie_jar_new();
2695 net->cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2697 load_all_cookies();
2699 g_signal_connect(G_OBJECT(net->file_cookie_jar), "changed",
2700 G_CALLBACK(update_cookie_jar), NULL);
2703 /* TA: XXX - we should be using this callback for any header-requests we
2704 * receive (hence the name "new_generic_request" -- but for now, its use
2705 * is limited to handling cookies.
2707 void
2708 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2710 SoupMessageHeaders *soup_msg_h;
2711 SoupURI *uri;
2712 char *cookie_str;
2714 soup_msg_h = soup_msg->request_headers;
2715 soup_message_headers_remove(soup_msg_h, "Cookie");
2716 uri = soup_message_get_uri(soup_msg);
2717 if ((cookie_str = get_cookies(uri))) {
2718 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2719 g_free(cookie_str);
2722 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2724 return;
2727 char *
2728 get_cookies(SoupURI *soup_uri) {
2729 char *cookie_str;
2731 cookie_str = soup_cookie_jar_get_cookies(client.net.file_cookie_jar, soup_uri, TRUE);
2733 return cookie_str;
2736 void
2737 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2739 GSList *resp_cookie = NULL, *cookie_list;
2740 SoupCookie *cookie;
2742 cookie_list = soup_cookies_from_response(soup_msg);
2743 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2745 SoupDate *soup_date;
2746 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2748 if (client.config.cookie_timeout && cookie->expires == NULL) {
2749 soup_date = soup_date_new_from_time_t(time(NULL) + client.config.cookie_timeout * 10);
2750 soup_cookie_set_expires(cookie, soup_date);
2751 soup_date_free(soup_date);
2753 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
2756 soup_cookies_free(cookie_list);
2758 return;
2761 void
2762 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2764 if (!new) {
2765 /* Nothing to do. */
2766 return;
2769 SoupCookie *copy;
2770 copy = soup_cookie_copy(new);
2772 soup_cookie_jar_add_cookie(client.net.session_cookie_jar, copy);
2774 return;
2777 void
2778 load_all_cookies(void)
2780 Network *net = &client.net;
2781 GSList *cookie_list;
2782 net->file_cookie_jar = soup_cookie_jar_text_new(net->cookie_store, COOKIES_STORAGE_READONLY);
2784 /* Put them back in the session store. */
2785 GSList *cookies_from_file = soup_cookie_jar_all_cookies(net->file_cookie_jar);
2786 cookie_list = cookies_from_file;
2788 for (; cookies_from_file;
2789 cookies_from_file = cookies_from_file->next)
2791 soup_cookie_jar_add_cookie(net->session_cookie_jar, cookies_from_file->data);
2794 soup_cookies_free(cookies_from_file);
2795 g_slist_free(cookie_list);
2797 return;
2800 #endif
2802 void
2803 mop_up(void) {
2804 /* Free up any nasty globals before exiting. */
2805 #ifdef ENABLE_COOKIE_SUPPORT
2806 if (client.net.cookie_store)
2807 g_free(client.net.cookie_store);
2808 #endif
2809 return;
2813 main(int argc, char *argv[]) {
2814 static Arg a;
2815 static char url[256] = "";
2816 static gboolean ver = false;
2817 static gboolean configfile_exists = FALSE;
2818 static const char *cfile = NULL;
2819 static gchar *winid = NULL;
2820 static GOptionEntry opts[] = {
2821 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2822 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2823 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2824 { NULL }
2826 static GError *err;
2827 args = argv;
2828 Config *config = &client.config;
2830 /* command line argument: version */
2831 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2832 g_printerr("can't init gtk: %s\n", err->message);
2833 g_error_free(err);
2834 return EXIT_FAILURE;
2837 if (ver) {
2838 printf("%s\n", INTERNAL_VERSION);
2839 return EXIT_SUCCESS;
2842 setup_client();
2844 if (getenv("TMPDIR")) {
2845 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2846 temp_dir[MAX_SETTING_SIZE-1] = 0;
2849 if( getenv("XDG_CONFIG_HOME") )
2850 config->config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2851 else
2852 config->config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2854 if (cfile)
2855 config->configfile = g_strdup(cfile);
2856 else
2857 config->configfile = g_strdup_printf(RCFILE);
2859 if (!g_thread_supported())
2860 g_thread_init(NULL);
2862 if (winid) {
2863 if (strncmp(winid, "0x", 2) == 0) {
2864 client.state.embed = strtol(winid, NULL, 16);
2865 } else {
2866 client.state.embed = atoi(winid);
2870 setup_modkeys();
2871 make_keyslist();
2872 setup_gui();
2873 #ifdef ENABLE_COOKIE_SUPPORT
2874 setup_cookies();
2875 #endif
2877 make_searchengines_list(searchengines, LENGTH(searchengines));
2878 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2880 /* Check if the specified file exists. */
2881 /* And only warn the user, if they explicitly asked for a config on the
2882 * command line.
2884 if (!(access(config->configfile, F_OK) == 0) && cfile) {
2885 echo_message(Info, "Config file '%s' doesn't exist", cfile);
2886 } else if ((access(config->configfile, F_OK) == 0))
2887 configfile_exists = true;
2889 /* read config file */
2890 /* But only report errors if we failed, and the file existed. */
2891 if ((SUCCESS != read_rcfile(config->configfile)) && configfile_exists) {
2892 echo_message(Error, "Error in config file '%s'", config->configfile);
2893 g_free(config->configfile);
2896 /* command line argument: URL */
2897 if (argc > 1) {
2898 strncpy(url, argv[argc - 1], 255);
2899 } else {
2900 strncpy(url, startpage, 255);
2903 a.i = TargetCurrent;
2904 a.s = url;
2905 open_arg(&a);
2906 gtk_main();
2908 mop_up();
2910 return EXIT_SUCCESS;