Fixed missed g_free (patch by Daniel Carl <danielcarl@gmx.de>)
[vimprobable2.git] / main.c
blob0078ad52240f04f5572ece9ac002d2351c34516f
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 success = FALSE, 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 success = 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 state->search_handle = g_strdup(&text[1]);
502 #endif
503 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
504 a.i = Silent;
505 a.s = g_strdup_printf("hints.fire();");
506 script(&a);
507 g_free(a.s);
508 update_state();
509 } else
510 return;
511 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
514 gboolean
515 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
516 Arg a;
517 int numval;
518 State *state = &client.state;
520 if (state->mode == ModeHints) {
521 if (event->keyval == GDK_Tab) {
522 a.i = Silent;
523 a.s = g_strdup_printf("hints.focusNextHint();");
524 script(&a);
525 g_free(a.s);
526 update_state();
527 return TRUE;
529 if (event->keyval == GDK_ISO_Left_Tab) {
530 a.i = Silent;
531 a.s = g_strdup_printf("hints.focusPreviousHint();");
532 script(&a);
533 g_free(a.s);
534 update_state();
535 return TRUE;
537 if (event->keyval == GDK_Return) {
538 a.i = Silent;
539 a.s = g_strdup_printf("hints.fire();");
540 script(&a);
541 g_free(a.s);
542 update_state();
543 return TRUE;
546 switch (event->keyval) {
547 case GDK_bracketleft:
548 case GDK_Escape:
549 if (!IS_ESCAPE(event)) break;
550 a.i = HideCompletion;
551 complete(&a);
552 a.i = ModeNormal;
553 state->commandpointer = 0;
554 return set(&a);
555 break;
556 case GDK_Tab:
557 a.i = DirectionNext;
558 return complete(&a);
559 break;
560 case GDK_Up:
561 a.i = DirectionPrev;
562 return commandhistoryfetch(&a);
563 break;
564 case GDK_Down:
565 a.i = DirectionNext;
566 return commandhistoryfetch(&a);
567 break;
568 case GDK_ISO_Left_Tab:
569 a.i = DirectionPrev;
570 return complete(&a);
571 break;
574 if (state->mode == ModeHints) {
575 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
576 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
577 (event->keyval == GDK_BackSpace)) {
578 state->count /= 10;
579 a.i = Silent;
580 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
581 script(&a);
582 g_free(a.s);
583 update_state();
584 return TRUE;
587 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
588 if ((numval >= 1 && numval <= 9) || (numval == 0 && state->count)) {
589 /* allow a zero as non-first number */
590 state->count = (state->count ? state->count * 10 : 0) + numval;
591 a.i = Silent;
592 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
593 script(&a);
594 g_free(a.s);
595 update_state();
596 return TRUE;
600 return FALSE;
603 gboolean
604 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
605 int i;
606 WebKitHitTestResult *result;
607 WebKitHitTestResultContext context;
608 State *state = &client.state;
609 if (state->mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
610 /* handle mouse click events */
611 for (i = 0; i < LENGTH(mouse); i++) {
612 if (mouse[i].mask == CLEAN(event->button.state)
613 && (mouse[i].modkey == state->current_modkey
614 || (!mouse[i].modkey && !state->current_modkey)
615 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
616 && mouse[i].button == event->button.button
617 && mouse[i].func) {
618 if (mouse[i].func(&mouse[i].arg)) {
619 state->current_modkey = state->count = 0;
620 update_state();
621 return TRUE;
625 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
626 g_object_get(result, "context", &context, NULL);
627 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
628 Arg a = { .i = ModeInsert };
629 set(&a);
630 state->manual_focus = TRUE;
632 } else if (state->mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
633 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
634 g_object_get(result, "context", &context, NULL);
635 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
636 Arg a = { .i = ModeNormal };
637 set(&a);
639 } else {
640 gchar *value = NULL, *message = NULL;
641 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
642 if (value && !strcmp(value, "[object HTMLFormElement]")) {
643 Arg a = { .i = ModeInsert, .s = NULL };
644 set(&a);
645 state->manual_focus = TRUE;
647 g_free(value);
648 g_free(message);
650 return FALSE;
653 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
654 Arg a;
655 guint16 length = gtk_entry_get_text_length(entry);
657 if (!length) {
658 a.i = HideCompletion;
659 complete(&a);
660 a.i = ModeNormal;
661 return set(&a);
663 return FALSE;
666 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
667 Arg a;
668 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
669 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
670 gboolean forward = FALSE;
672 /* Update incremental search if the user changes the search text.
674 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
675 * from the user. But if the entry is focused and the text is set
676 * through gtk_entry_set_text() in some asyncrounous operation,
677 * I would consider that a bug.
680 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
681 webkit_web_view_unmark_text_matches(client.gui.webview);
682 webkit_web_view_search_text(client.gui.webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
683 return TRUE;
684 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
685 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
686 a.i = Silent;
687 switch (text[0]) {
688 case '.':
689 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
690 break;
692 case ',':
693 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
694 break;
696 case ';':
697 a.s = NULL;
698 switch (text[1]) {
699 case 's':
700 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
701 break;
702 case 'y':
703 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
704 break;
705 case 'o':
706 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
707 break;
708 case 't': case 'w':
709 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
710 break;
711 case 'O': case 'T': case 'W':
712 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
713 break;
714 case 'i':
715 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
716 break;
717 case 'I':
718 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
719 break;
721 break;
723 client.state.count = 0;
724 if (a.s) {
725 script(&a);
726 g_free(a.s);
729 return TRUE;
730 } else if (length == 0) {
731 client.state.mode = ModeNormal;
732 a.i = Silent;
733 a.s = g_strdup("hints.clearHints();");
734 script(&a);
735 g_free(a.s);
736 client.state.count = 0;
737 update_state();
740 return FALSE;
743 /* funcs here */
745 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
746 memset(suggline, 0, 512);
747 strncpy(suggline, command, 512);
748 strncat(suggline, " ", 1);
749 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
752 GtkWidget * fill_eventbox(const char * completion_line) {
753 GtkBox * row;
754 GtkWidget *row_eventbox, *el;
755 GdkColor color;
756 char *markup, *markup_tmp;
758 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
759 row_eventbox = gtk_event_box_new();
760 gdk_color_parse(completionbgcolor[0], &color);
761 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
762 el = gtk_label_new(NULL);
763 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
764 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
765 markup_tmp, "</span>", NULL);
766 gtk_label_set_markup(GTK_LABEL(el), markup);
767 g_free(markup_tmp);
768 g_free(markup);
769 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
770 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
771 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
772 return row_eventbox;
775 gboolean
776 complete(const Arg *arg) {
777 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
778 size_t listlen, len, cmdlen;
779 int i, spacepos;
780 Listelement *elementlist = NULL, *elementpointer;
781 gboolean highlight = FALSE;
782 GtkBox *row;
783 GtkWidget *row_eventbox, *el;
784 GtkBox *_table;
785 GdkColor color;
786 static GtkWidget *table, *top_border;
787 static char *prefix;
788 static char **suggestions;
789 static GtkWidget **widgets;
790 static int n = 0, m, current = -1;
791 Gui *gui = &client.gui;
793 str = (char*)gtk_entry_get_text(GTK_ENTRY(gui->inputbox));
794 len = strlen(str);
796 /* Get the length of the list of commands for completion. We need this to
797 * malloc/realloc correctly.
799 listlen = LENGTH(commands);
801 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
802 return TRUE;
803 if (prefix) {
804 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
805 gdk_color_parse(completionbgcolor[0], &color);
806 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
807 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
808 if ((arg->i == DirectionNext && current == 0)
809 || (arg->i == DirectionPrev && current == n - 1))
810 current = -1;
811 } else {
812 free(widgets);
813 free(suggestions);
814 free(prefix);
815 gtk_widget_destroy(GTK_WIDGET(table));
816 gtk_widget_destroy(GTK_WIDGET(top_border));
817 table = NULL;
818 widgets = NULL;
819 suggestions = NULL;
820 prefix = NULL;
821 n = 0;
822 current = -1;
823 if (arg->i == HideCompletion)
824 return TRUE;
826 } else if (arg->i == HideCompletion)
827 return TRUE;
828 if (!widgets) {
829 prefix = g_strdup(str);
830 widgets = malloc(sizeof(GtkWidget*) * listlen);
831 suggestions = malloc(sizeof(char*) * listlen);
832 top_border = gtk_event_box_new();
833 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
834 gdk_color_parse(completioncolor[2], &color);
835 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
836 table = gtk_event_box_new();
837 gdk_color_parse(completionbgcolor[0], &color);
838 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
839 highlight = len > 1;
840 if (strchr(str, ' ') == NULL) {
841 /* command completion */
842 listlen = LENGTH(commands);
843 for (i = 0; i < listlen; i++) {
844 if (commands[i].cmd == NULL)
845 break;
846 cmdlen = strlen(commands[i].cmd);
847 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
848 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
849 if (highlight) {
850 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
851 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
852 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
853 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
855 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
856 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
857 row_eventbox = gtk_event_box_new();
858 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
859 el = gtk_label_new(NULL);
860 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
861 free(s);
862 gtk_label_set_markup(GTK_LABEL(el), markup);
863 g_free(markup);
864 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
865 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
866 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
867 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
868 suggestions[n] = commands[i].cmd;
869 widgets[n++] = row_eventbox;
872 } else {
873 entry = (char *)malloc(512 * sizeof(char));
874 if (entry == NULL) {
875 return FALSE;
877 memset(entry, 0, 512);
878 suggurls = malloc(sizeof(char*) * listlen);
879 if (suggurls == NULL) {
880 return FALSE;
882 spacepos = strcspn(str, " ");
883 searchfor = (str + spacepos + 1);
884 strncpy(command, (str + 1), spacepos - 1);
885 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
886 /* browser settings */
887 listlen = LENGTH(browsersettings);
888 for (i = 0; i < listlen; i++) {
889 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
890 /* match */
891 fill_suggline(suggline, command, browsersettings[i].name);
892 /* FIXME(HP): This memory is never freed */
893 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
894 strncpy(suggurls[n], suggline, 512);
895 suggestions[n] = suggurls[n];
896 row_eventbox = fill_eventbox(suggline);
897 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
898 widgets[n++] = row_eventbox;
902 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
903 /* completion on tags */
904 spacepos = strcspn(str, " ");
905 searchfor = (str + spacepos + 1);
906 elementlist = complete_list(searchfor, 1, elementlist);
907 } else {
908 /* URL completion: bookmarks */
909 elementlist = complete_list(searchfor, 0, elementlist);
910 m = count_list(elementlist);
911 if (m < MAX_LIST_SIZE) {
912 /* URL completion: history */
913 elementlist = complete_list(searchfor, 2, elementlist);
916 elementpointer = elementlist;
917 while (elementpointer != NULL) {
918 fill_suggline(suggline, command, elementpointer->element);
919 /* FIXME(HP): This memory is never freed */
920 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
921 strncpy(suggurls[n], suggline, 512);
922 suggestions[n] = suggurls[n];
923 row_eventbox = fill_eventbox(suggline);
924 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
925 widgets[n++] = row_eventbox;
926 elementpointer = elementpointer->next;
927 if (n >= MAX_LIST_SIZE)
928 break;
930 free_list(elementlist);
931 if (suggurls != NULL) {
932 free(suggurls);
933 suggurls = NULL;
935 if (entry != NULL) {
936 free(entry);
937 entry = NULL;
940 /* TA: FIXME - this needs rethinking entirely. */
942 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
943 if (widgets_temp == NULL && widgets == NULL) {
944 fprintf(stderr, "Couldn't realloc() widgets\n");
945 exit(1);
947 widgets = widgets_temp;
948 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
949 if (suggestions_temp == NULL && suggestions == NULL) {
950 fprintf(stderr, "Couldn't realloc() suggestions\n");
951 exit(1);
953 suggestions = suggestions_temp;
955 if (!n) {
956 gdk_color_parse(completionbgcolor[1], &color);
957 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
958 el = gtk_label_new(NULL);
959 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
960 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
961 gtk_label_set_markup(GTK_LABEL(el), markup);
962 g_free(markup);
963 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
965 gtk_box_pack_start(gui->box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
966 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
967 gtk_box_pack_start(gui->box, GTK_WIDGET(table), FALSE, FALSE, 0);
968 gtk_widget_show_all(GTK_WIDGET(gui->window));
969 if (!n)
970 return TRUE;
971 current = arg->i == DirectionPrev ? n - 1 : 0;
973 if (current != -1) {
974 gdk_color_parse(completionbgcolor[2], &color);
975 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
976 s = g_strconcat(":", suggestions[current], NULL);
977 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), s);
978 g_free(s);
979 } else
980 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), prefix);
981 gtk_editable_set_position(GTK_EDITABLE(gui->inputbox), -1);
982 return TRUE;
985 gboolean
986 descend(const Arg *arg) {
987 char *source = (char*)webkit_web_view_get_uri(client.gui.webview), *p = &source[0], *new;
988 int i, len;
989 client.state.count = client.state.count ? client.state.count : 1;
991 if (!source)
992 return TRUE;
993 if (arg->i == Rootdir) {
994 for (i = 0; i < 3; i++) /* get to the third slash */
995 if (!(p = strchr(++p, '/')))
996 return TRUE; /* if we cannot find it quit */
997 } else {
998 len = strlen(source);
999 if (!len) /* if string is empty quit */
1000 return TRUE;
1001 p = source + len; /* start at the end */
1002 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1003 ++client.state.count;
1004 for (i = 0; i < client.state.count; i++)
1005 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1006 if (p == source) /* if we reach the first char pointer quit */
1007 return TRUE;
1008 ++p; /* since we do p-- in the while, we are pointing at
1009 the char before the slash, so +1 */
1011 len = p - source + 1; /* new length = end - start + 1 */
1012 new = malloc(len + 1);
1013 memcpy(new, source, len);
1014 new[len] = '\0';
1015 webkit_web_view_load_uri(client.gui.webview, new);
1016 free(new);
1017 return TRUE;
1020 gboolean
1021 echo(const Arg *arg) {
1022 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1024 if (index < Info || index > Error)
1025 return TRUE;
1027 if (!gtk_widget_is_focus(GTK_WIDGET(client.gui.inputbox))) {
1028 set_widget_font_and_color(client.gui.inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1029 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), !arg->s ? "" : arg->s);
1032 return TRUE;
1035 static gboolean
1036 open_inspector(const Arg * arg) {
1037 gboolean inspect_enabled;
1038 WebKitWebSettings *settings;
1040 settings = webkit_web_view_get_settings(client.gui.webview);
1041 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1042 if (inspect_enabled) {
1043 webkit_web_inspector_show(client.gui.inspector);
1044 return TRUE;
1045 } else {
1046 echo_message(Error, "Webinspector is not enabled");
1047 return FALSE;
1051 gboolean
1052 input(const Arg *arg) {
1053 int pos = 0;
1054 client.state.count = 0;
1055 const char *url;
1056 int index = Info;
1057 Arg a;
1058 GtkWidget *inputbox = client.gui.inputbox;
1060 /* if inputbox hidden, show it again */
1061 if (!gtk_widget_get_visible(inputbox))
1062 gtk_widget_set_visible(inputbox, TRUE);
1064 update_state();
1066 /* Set the colour and font back to the default, so that we don't still
1067 * maintain a red colour from a warning from an end of search indicator,
1068 * etc.
1070 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1072 /* to avoid things like :open URL :open URL2 or :open :open URL */
1073 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1074 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1075 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(client.gui.webview)))
1076 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1078 gtk_widget_grab_focus(inputbox);
1079 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1081 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1082 client.state.mode = ModeHints;
1083 a.i = Silent;
1084 switch (arg->s[0]) {
1085 case '.':
1086 a.s = g_strdup("hints.createHints('', 'f');");
1087 break;
1089 case ',':
1090 a.s = g_strdup("hints.createHints('', 'F');");
1091 break;
1093 case ';':
1094 a.s = NULL;
1095 if (arg->s[1]) {
1096 switch (arg->s[1]) {
1097 case 's':
1098 a.s = g_strdup("hints.createHints('', 's');");
1099 break;
1100 case 'y':
1101 a.s = g_strdup("hints.createHints('', 'y');");
1102 break;
1103 case 'o':
1104 a.s = g_strdup("hints.createHints('', 'f');");
1105 break;
1106 case 't': case 'w':
1107 a.s = g_strdup("hints.createHints('', 'F');");
1108 break;
1109 case 'O': case 'T': case 'W':
1110 a.s = g_strdup("hints.createHints('', 'O');");
1111 break;
1112 case 'i':
1113 a.s = g_strdup("hints.createHints('', 'i');");
1114 break;
1115 case 'I':
1116 a.s = g_strdup("hints.createHints('', 'I');");
1117 break;
1120 break;
1122 client.state.count = 0;
1123 if (a.s) {
1124 script(&a);
1125 g_free(a.s);
1129 return TRUE;
1132 gboolean
1133 navigate(const Arg *arg) {
1134 if (arg->i & NavigationForwardBack)
1135 webkit_web_view_go_back_or_forward(client.gui.webview, (arg->i == NavigationBack ? -1 : 1) * (client.state.count ? client.state.count : 1));
1136 else if (arg->i & NavigationReloadActions)
1137 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(client.gui.webview);
1138 else
1139 webkit_web_view_stop_loading(client.gui.webview);
1140 return TRUE;
1143 gboolean
1144 number(const Arg *arg) {
1145 const char *source = webkit_web_view_get_uri(client.gui.webview);
1146 char *uri, *p, *new;
1147 int number, diff = (client.state.count ? client.state.count : 1) * (arg->i == Increment ? 1 : -1);
1149 if (!source)
1150 return TRUE;
1151 uri = g_strdup(source); /* copy string */
1152 p =& uri[0];
1153 while(*p != '\0') /* goto the end of the string */
1154 ++p;
1155 --p;
1156 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1157 --p;
1158 if (*(++p) == '\0') { /* if no numbers were found abort */
1159 free(uri);
1160 return TRUE;
1162 number = atoi(p) + diff; /* apply diff on number */
1163 *p = '\0';
1164 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1165 webkit_web_view_load_uri(client.gui.webview, new);
1166 g_free(new);
1167 free(uri);
1168 return TRUE;
1171 gboolean
1172 open_arg(const Arg *arg) {
1173 char *argv[64];
1174 char *s = arg->s, *p = NULL, *new;
1175 Arg a = { .i = NavigationReload };
1176 int len;
1177 char *search_uri, *search_term;
1179 if (client.state.embed) {
1180 gchar winid[64];
1181 snprintf(winid, LENGTH(winid), "%u", (gint)client.state.embed);
1182 argv[0] = *args;
1183 argv[1] = "-e";
1184 argv[2] = winid;
1185 argv[3] = arg->s;
1186 argv[4] = NULL;
1187 } else {
1188 argv[0] = *args;
1189 argv[1] = arg->s;
1190 argv[2] = NULL;
1193 if (!arg->s)
1194 navigate(&a);
1195 else if (arg->i == TargetCurrent) {
1196 while(*s == ' ') /* strip leading whitespace */
1197 ++s;
1198 p = (s + strlen(s) - 1);
1199 while(*p == ' ') /* strip trailing whitespace */
1200 --p;
1201 *(p + 1) = '\0';
1202 len = strlen(s);
1203 new = NULL;
1204 /* check for external handlers */
1205 if (open_handler(s))
1206 return TRUE;
1207 /* check for search engines */
1208 p = strchr(s, ' ');
1209 if (p) { /* check for search engines */
1210 *p = '\0';
1211 search_uri = find_uri_for_searchengine(s);
1212 if (search_uri != NULL) {
1213 search_term = soup_uri_encode(p+1, "&");
1214 new = g_strdup_printf(search_uri, search_term);
1215 g_free(search_term);
1217 *p = ' ';
1219 if (!new) {
1220 if (len > 3 && strstr(s, "://")) { /* valid url? */
1221 p = new = g_malloc(len + 1);
1222 while(*s != '\0') { /* strip whitespaces */
1223 if (*s != ' ')
1224 *(p++) = *s;
1225 ++s;
1227 *p = '\0';
1228 } else if (strcspn(s, "/") == 0 || strcspn(s, "./") == 0) { /* prepend "file://" */
1229 new = g_malloc(sizeof("file://") + len);
1230 strcpy(new, "file://");
1231 memcpy(&new[sizeof("file://") - 1], s, len + 1);
1232 } else if (p || !strchr(s, '.')) { /* whitespaces or no dot? */
1233 search_uri = find_uri_for_searchengine(defaultsearch);
1234 if (search_uri != NULL) {
1235 search_term = soup_uri_encode(s, "&");
1236 new = g_strdup_printf(search_uri, search_term);
1237 g_free(search_term);
1239 } else { /* prepend "http://" */
1240 new = g_malloc(sizeof("http://") + len);
1241 strcpy(new, "http://");
1242 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1245 webkit_web_view_load_uri(client.gui.webview, new);
1246 g_free(new);
1247 } else
1248 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1249 return TRUE;
1252 gboolean
1253 open_remembered(const Arg *arg)
1255 Arg a = {arg->i, client.state.rememberedURI};
1257 if (strcmp(client.state.rememberedURI, "")) {
1258 open_arg(&a);
1260 return TRUE;
1263 gboolean
1264 yank(const Arg *arg) {
1265 const char *url, *content;
1267 if (arg->i & SourceSelection) {
1268 webkit_web_view_copy_clipboard(client.gui.webview);
1269 if (arg->i & ClipboardPrimary)
1270 content = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1271 if (!content && arg->i & ClipboardGTK)
1272 content = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1273 if (content) {
1274 echo_message(Info, "Yanked %s", content);
1275 g_free((gpointer *)content);
1277 } else {
1278 if (arg->i & SourceURL) {
1279 url = webkit_web_view_get_uri(client.gui.webview);
1280 } else {
1281 url = arg->s;
1283 if (!url)
1284 return TRUE;
1286 echo_message(Info, "Yanked %s", url);
1287 if (arg->i & ClipboardPrimary)
1288 gtk_clipboard_set_text(client.state.clipboards[0], url, -1);
1289 if (arg->i & ClipboardGTK)
1290 gtk_clipboard_set_text(client.state.clipboards[1], url, -1);
1292 return TRUE;
1295 gboolean
1296 paste(const Arg *arg) {
1297 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1299 /* If we're over a link, open it in a new target. */
1300 if (strlen(client.state.rememberedURI) > 0) {
1301 Arg new_target = { .i = TargetNew, .s = arg->s };
1302 open_arg(&new_target);
1303 return TRUE;
1306 if (arg->i & ClipboardPrimary)
1307 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1308 if (!a.s && arg->i & ClipboardGTK)
1309 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1310 if (a.s) {
1311 open_arg(&a);
1312 g_free(a.s);
1314 return TRUE;
1317 gboolean
1318 quit(const Arg *arg) {
1319 FILE *f;
1320 const char *filename;
1321 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1322 if (uri != NULL) {
1323 /* write last URL into status file for recreation with "u" */
1324 filename = g_strdup_printf("%s", client.config.config_base);
1325 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1326 f = fopen(filename, "w");
1327 g_free((gpointer *)filename);
1328 if (f != NULL) {
1329 fprintf(f, "%s", uri);
1330 fclose(f);
1333 gtk_main_quit();
1334 return TRUE;
1337 gboolean
1338 revive(const Arg *arg) {
1339 FILE *f;
1340 const char *filename;
1341 char buffer[512] = "";
1342 Arg a = { .i = TargetNew, .s = NULL };
1343 /* get the URL of the window which has been closed last */
1344 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1345 f = fopen(filename, "r");
1346 g_free((gpointer *)filename);
1347 if (f != NULL) {
1348 fgets(buffer, 512, f);
1349 fclose(f);
1351 if (strlen(buffer) > 0) {
1352 a.s = buffer;
1353 open_arg(&a);
1354 return TRUE;
1356 return FALSE;
1359 static
1360 gboolean print_frame(const Arg *arg)
1362 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1363 webkit_web_frame_print (frame);
1364 return TRUE;
1367 gboolean
1368 search(const Arg *arg) {
1369 State *state = &client.state;
1370 state->count = state->count ? state->count : 1;
1371 gboolean success, direction = arg->i & DirectionPrev;
1373 if (arg->s) {
1374 free(state->search_handle);
1375 state->search_handle = g_strdup(arg->s);
1377 if (!state->search_handle)
1378 return TRUE;
1379 if (arg->i & DirectionAbsolute)
1380 state->search_direction = direction;
1381 else
1382 direction ^= state->search_direction;
1383 do {
1384 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, FALSE);
1385 if (!success) {
1386 if (arg->i & Wrapping) {
1387 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, TRUE);
1388 if (success) {
1389 echo_message(Warning, "search hit %s, continuing at %s",
1390 direction ? "BOTTOM" : "TOP",
1391 direction ? "TOP" : "BOTTOM");
1392 } else
1393 break;
1394 } else
1395 break;
1397 } while(--state->count);
1398 if (!success) {
1399 echo_message(Error, "Pattern not found: %s", state->search_handle);
1401 return TRUE;
1404 gboolean
1405 set(const Arg *arg) {
1406 switch (arg->i) {
1407 case ModeNormal:
1408 if (client.state.search_handle) {
1409 client.state.search_handle = NULL;
1410 webkit_web_view_unmark_text_matches(client.gui.webview);
1412 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), "");
1413 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1414 break;
1415 case ModePassThrough:
1416 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1417 break;
1418 case ModeSendKey:
1419 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1420 break;
1421 case ModeInsert: /* should not be called manually but automatically */
1422 echo_message(Info | NoAutoHide, "-- INSERT --");
1423 break;
1424 default:
1425 return TRUE;
1427 client.state.mode = arg->i;
1428 return TRUE;
1431 gchar*
1432 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1433 JSStringRef string_ref;
1434 gchar *string;
1435 size_t length;
1437 string_ref = JSValueToStringCopy(context, ref, NULL);
1438 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1439 string = g_new(gchar, length);
1440 JSStringGetUTF8CString(string_ref, string, length);
1441 JSStringRelease(string_ref);
1442 return string;
1445 void
1446 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1447 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1448 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1449 JSStringRef str;
1450 JSValueRef val, exception;
1452 str = JSStringCreateWithUTF8CString(script);
1453 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1454 JSStringRelease(str);
1455 if (!val)
1456 *message = jsapi_ref_to_string(context, exception);
1457 else
1458 *value = jsapi_ref_to_string(context, val);
1461 gboolean
1462 quickmark(const Arg *a) {
1463 int i, b;
1464 b = atoi(a->s);
1465 char *fn = g_strdup_printf(QUICKMARK_FILE);
1466 FILE *fp;
1467 fp = fopen(fn, "r");
1468 g_free(fn);
1469 fn = NULL;
1470 char buf[100];
1472 if (fp != NULL && b < 10) {
1473 for( i=0; i < b; ++i ) {
1474 if (feof(fp)) {
1475 break;
1477 fgets(buf, 100, fp);
1479 char *ptr = strrchr(buf, '\n');
1480 *ptr = '\0';
1481 if (strlen(buf)) {
1482 Arg x = { .s = buf };
1483 return open_arg(&x);
1484 } else {
1485 echo_message(Error, "Quickmark %d not defined", b);
1486 return false;
1488 } else { return false; }
1491 gboolean
1492 script(const Arg *arg) {
1493 gchar *value = NULL, *message = NULL;
1494 char text[BUF_SIZE] = "";
1495 Arg a;
1496 WebKitNetworkRequest *request;
1497 WebKitDownload *download;
1499 if (!arg->s) {
1500 set_error("Missing argument.");
1501 return FALSE;
1503 jsapi_evaluate_script(arg->s, &value, &message);
1504 if (message) {
1505 set_error(message);
1506 g_free(value);
1507 g_free(message);
1508 return FALSE;
1510 g_free(message);
1511 if (arg->i != Silent && value) {
1512 echo_message(arg->i, value);
1514 /* switch mode according to scripts return value */
1515 if (value) {
1516 if (strncmp(value, "done;", 5) == 0) {
1517 a.i = ModeNormal;
1518 set(&a);
1519 } else if (strncmp(value, "insert;", 7) == 0) {
1520 a.i = ModeInsert;
1521 set(&a);
1522 client.state.manual_focus = TRUE;
1523 } else if (strncmp(value, "save;", 5) == 0) {
1524 /* forced download */
1525 a.i = ModeNormal;
1526 set(&a);
1527 request = webkit_network_request_new((value + 5));
1528 download = webkit_download_new(request);
1529 webview_download_cb(client.gui.webview, download, (gpointer *)NULL);
1530 } else if (strncmp(value, "yank;", 5) == 0) {
1531 /* yank link URL to clipboard */
1532 a.i = ModeNormal;
1533 set(&a);
1534 a.i = ClipboardPrimary | ClipboardGTK;
1535 a.s = (value + 5);
1536 yank(&a);
1537 } else if (strncmp(value, "colon;", 6) == 0) {
1538 /* use link URL for colon command */
1539 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(client.gui.inputbox)), 1023);
1540 a.i = ModeNormal;
1541 set(&a);
1542 switch (text[1]) {
1543 case 'O':
1544 a.s = g_strconcat(":open ", (value + 6), NULL);
1545 break;
1546 case 'T': case 'W':
1547 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1548 break;
1550 if (a.s) {
1551 input(&a);
1552 g_free(a.s);
1554 } else if (strncmp(value, "error;", 6) == 0) {
1555 a.i = Error;
1556 set(&a);
1559 g_free(value);
1560 return TRUE;
1563 gboolean
1564 scroll(const Arg *arg) {
1565 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? client.gui.adjust_h : client.gui.adjust_v;
1566 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1567 float val = gtk_adjustment_get_value(adjust) / max * 100;
1568 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1569 unsigned int count = client.state.count;
1571 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1572 if (arg->i & ScrollMove)
1573 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1574 direction * /* direction */
1575 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1576 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1577 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1578 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1579 else
1580 gtk_adjustment_set_value(adjust,
1581 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1582 update_state();
1584 return TRUE;
1587 gboolean
1588 zoom(const Arg *arg) {
1589 unsigned int count = client.state.count;
1591 webkit_web_view_set_full_content_zoom(client.gui.webview, (arg->i & ZoomFullContent) > 0);
1592 webkit_web_view_set_zoom_level(client.gui.webview, (arg->i & ZoomOut) ?
1593 webkit_web_view_get_zoom_level(client.gui.webview) +
1594 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * client.config.zoomstep) :
1595 (count ? (float)count / 100.0 : 1.0));
1596 return TRUE;
1599 gboolean
1600 fake_key_event(const Arg *a) {
1601 if(!client.state.embed) {
1602 return FALSE;
1604 Display *xdpy;
1605 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1606 echo_message(Error, "Couldn't find the XDisplay.");
1607 return FALSE;
1610 XKeyEvent xk;
1611 xk.display = xdpy;
1612 xk.subwindow = None;
1613 xk.time = CurrentTime;
1614 xk.same_screen = True;
1615 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1616 xk.window = client.state.embed;
1617 xk.state = a->i;
1619 if( ! a->s ) {
1620 echo_message(Error, "Zero pointer as argument! Check your config.h");
1621 return FALSE;
1624 KeySym keysym;
1625 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1626 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1627 return FALSE;
1630 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1631 echo_message(Error, "Couldn't translate keysym to keycode");
1632 return FALSE;
1635 xk.type = KeyPress;
1636 if( !XSendEvent(xdpy, client.state.embed, True, KeyPressMask, (XEvent *)&xk) ) {
1637 echo_message(Error, "XSendEvent failed");
1638 return FALSE;
1640 XFlush(xdpy);
1642 return TRUE;
1645 gboolean
1646 commandhistoryfetch(const Arg *arg) {
1647 State *state = &client.state;
1648 const int length = g_list_length(client.state.commandhistory);
1649 gchar *input_message = NULL;
1651 if (length > 0) {
1652 if (arg->i == DirectionPrev) {
1653 state->commandpointer = (length + state->commandpointer - 1) % length;
1654 } else {
1655 state->commandpointer = (length + state->commandpointer + 1) % length;
1658 const char* command = (char *)g_list_nth_data(state->commandhistory, state->commandpointer);
1659 input_message = g_strconcat(":", command, NULL);
1660 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), input_message);
1661 g_free(input_message);
1662 gtk_editable_set_position(GTK_EDITABLE(client.gui.inputbox), -1);
1663 return TRUE;
1666 return FALSE;
1669 gboolean
1670 bookmark(const Arg *arg) {
1671 FILE *f;
1672 const char *filename;
1673 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1674 const char *title = webkit_web_view_get_title(client.gui.webview);
1675 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1676 f = fopen(filename, "a");
1677 g_free((gpointer *)filename);
1678 if (uri == NULL || strlen(uri) == 0) {
1679 set_error("No URI found to bookmark.");
1680 return FALSE;
1682 if (f != NULL) {
1683 fprintf(f, "%s", uri);
1684 if (title != NULL) {
1685 fprintf(f, "%s", " ");
1686 fprintf(f, "%s", title);
1688 if (arg->s && strlen(arg->s)) {
1689 build_taglist(arg, f);
1691 fprintf(f, "%s", "\n");
1692 fclose(f);
1693 echo_message(Info, "Bookmark saved");
1694 return TRUE;
1695 } else {
1696 set_error("Bookmarks file not found.");
1697 return FALSE;
1701 gboolean
1702 history() {
1703 FILE *f;
1704 const char *filename;
1705 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1706 const char *title = webkit_web_view_get_title(client.gui.webview);
1707 char *entry, buffer[512], *new;
1708 int n, i = 0;
1709 gboolean finished = FALSE;
1710 if (uri != NULL) {
1711 if (title != NULL) {
1712 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1713 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1714 } else {
1715 entry = malloc((strlen(uri) + 1) * sizeof(char));
1716 memset(entry, 0, strlen(uri) + 1);
1718 if (entry != NULL) {
1719 strncpy(entry, uri, strlen(uri));
1720 if (title != NULL) {
1721 strncat(entry, " ", 1);
1722 strncat(entry, title, strlen(title));
1724 n = strlen(entry);
1725 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1726 f = fopen(filename, "r");
1727 if (f != NULL) {
1728 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1729 if (new != NULL) {
1730 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1731 /* newest entries go on top */
1732 strncpy(new, entry, strlen(entry));
1733 strncat(new, "\n", 1);
1734 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1735 while (finished != TRUE) {
1736 if ((char *)NULL == fgets(buffer, 512, f)) {
1737 /* check if end of file was reached / error occured */
1738 if (!feof(f)) {
1739 break;
1741 /* end of file reached */
1742 finished = TRUE;
1743 continue;
1745 /* compare line (-1 because of newline character) */
1746 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1747 /* if the URI is already in history; we put it on top and skip it here */
1748 strncat(new, buffer, 512);
1749 i++;
1751 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1752 break;
1755 fclose(f);
1757 f = fopen(filename, "w");
1758 g_free((gpointer *)filename);
1759 if (f != NULL) {
1760 fprintf(f, "%s", new);
1761 fclose(f);
1763 if (new != NULL) {
1764 free(new);
1765 new = NULL;
1769 if (entry != NULL) {
1770 free(entry);
1771 entry = NULL;
1774 return TRUE;
1777 static gboolean
1778 view_source(const Arg * arg) {
1779 gboolean current_mode = webkit_web_view_get_view_source_mode(client.gui.webview);
1780 webkit_web_view_set_view_source_mode(client.gui.webview, !current_mode);
1781 webkit_web_view_reload(client.gui.webview);
1782 return TRUE;
1785 /* open an external editor defined by the protocol handler for
1786 vimprobableedit on a text box or similar */
1787 static gboolean
1788 open_editor(const Arg *arg) {
1789 char *text = NULL;
1790 gboolean success;
1791 GPid child_pid;
1792 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1793 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1794 temp_dir);
1795 int temp_file_handle = -1;
1797 /* check if active element is suitable for text editing */
1798 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1799 if (value == NULL) {
1800 g_free(message);
1801 return FALSE;
1803 tag = g_strdup(value);
1804 if (strcmp(tag, "INPUT") == 0) {
1805 /* extra check: type == text */
1806 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1807 if (strcmp(value, "text") != 0) {
1808 g_free(value);
1809 g_free(message);
1810 return FALSE;
1812 g_free(value);
1813 g_free(message);
1814 } else if (strcmp(tag, "TEXTAREA") != 0) {
1815 g_free(value);
1816 g_free(message);
1817 return FALSE;
1819 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1820 text = g_strdup(value);
1821 if (text == NULL) {
1822 g_free(value);
1823 g_free(message);
1824 return FALSE;
1827 /* write text into temporary file */
1828 temp_file_handle = mkstemp(temp_file_name);
1829 if (temp_file_handle == -1) {
1830 message = g_strdup_printf("Could not create temporary file: %s",
1831 strerror(errno));
1832 echo_message(Error, message);
1833 g_free(value);
1834 g_free(message);
1835 g_free(text);
1836 return FALSE;
1838 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1839 message = g_strdup_printf("Short write to temporary file: %s",
1840 strerror(errno));
1841 echo_message(Error, message);
1842 g_free(value);
1843 g_free(message);
1844 g_free(text);
1845 return FALSE;
1847 close(temp_file_handle);
1848 g_free(text);
1850 /* spawn editor */
1851 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1852 success = open_handler_pid(edit_url, &child_pid);
1853 g_free(edit_url);
1854 if (!success) {
1855 echo_message(Error, "External editor open failed (no handler for"
1856 " vimprobableedit protocol?)");
1857 unlink(temp_file_name);
1858 g_free(value);
1859 g_free(message);
1860 return FALSE;
1863 /* mark the active text box as "under processing" */
1864 jsapi_evaluate_script(
1865 "document.activeElement.disabled = true;"
1866 "document.activeElement.originalBackground = "
1867 " document.activeElement.style.background;"
1868 "document.activeElement.style.background = '#aaaaaa';"
1869 ,&value, &message);
1871 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1873 /* temp_file_name is freed in _resume_from_editor */
1874 g_free(value);
1875 g_free(message);
1876 g_free(tag);
1877 return TRUE;
1881 /* pick up from where open_editor left the work to the glib event loop.
1883 This is called when the external editor exits.
1885 The data argument points to allocated memory containing the temporary file
1886 name. */
1887 void
1888 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
1889 FILE *fp;
1890 GString *set_value_js = g_string_new(
1891 "document.activeElement.value = \"");
1892 g_spawn_close_pid(child_pid);
1893 gchar *value = NULL, *message = NULL;
1894 gchar *temp_file_name = data;
1895 gchar buffer[BUF_SIZE] = "";
1896 gchar *buf_ptr = buffer;
1897 int char_read;
1899 jsapi_evaluate_script(
1900 "document.activeElement.disabled = true;"
1901 "document.activeElement.style.background = '#aaaaaa';"
1902 ,&value, &message);
1903 g_free(value);
1904 g_free(message);
1906 if (child_status) {
1907 echo_message(Error, "External editor returned with non-zero status,"
1908 " discarding edits.");
1909 goto error_exit;
1912 /* re-read the new contents of the file and put it into the HTML element */
1913 if (!access(temp_file_name, R_OK) == 0) {
1914 message = g_strdup_printf("Could not access temporary file: %s",
1915 strerror(errno));
1916 goto error_exit;
1918 fp = fopen(temp_file_name, "r");
1919 if (fp == NULL) {
1920 /* this would be too weird to even emit an error message */
1921 goto error_exit;
1923 jsapi_evaluate_script("document.activeElement.value = '';",
1924 &value, &message);
1925 g_free(value);
1926 g_free(message);
1928 while (EOF != (char_read = fgetc(fp))) {
1929 if (char_read == '\n') {
1930 *buf_ptr++ = '\\';
1931 *buf_ptr++ = 'n';
1932 } else if (char_read == '"') {
1933 *buf_ptr++ = '\\';
1934 *buf_ptr++ = '"';
1935 } else {
1936 *buf_ptr++ = char_read;
1938 /* ship out as the buffer when space gets tight. This has
1939 fuzz to save on thinking, plus we have enough space for the
1940 trailing "; in any case. */
1941 if (buf_ptr-buffer>=BUF_SIZE-10) {
1942 *buf_ptr = 0;
1943 g_string_append(set_value_js, buffer);
1944 buf_ptr = buffer;
1947 *buf_ptr++ = '"';
1948 *buf_ptr++ = ';';
1949 *buf_ptr = 0;
1950 g_string_append(set_value_js, buffer);
1951 fclose(fp);
1953 jsapi_evaluate_script(set_value_js->str, &value, &message);
1954 g_free(value);
1955 g_free(message);
1957 /* Fall through, error and normal exit are identical */
1958 error_exit:
1959 jsapi_evaluate_script(
1960 "document.activeElement.disabled = false;"
1961 "document.activeElement.style.background ="
1962 " document.activeElement.originalBackground;"
1963 ,&value, &message);
1965 g_string_free(set_value_js, TRUE);
1966 unlink(temp_file_name);
1967 g_free(temp_file_name);
1968 g_free(value);
1969 g_free(message);
1972 static gboolean
1973 focus_input(const Arg *arg) {
1974 static Arg a;
1976 a.s = g_strdup("hints.focusInput();");
1977 a.i = Silent;
1978 script(&a);
1979 g_free(a.s);
1980 update_state();
1981 client.state.manual_focus = TRUE;
1982 return TRUE;
1985 static void
1986 clear_focus(void) {
1987 static Arg a;
1989 a.s = g_strdup("hints.clearFocus();");
1990 a.i = Silent;
1991 script(&a);
1992 g_free(a.s);
1993 a.i = ModeNormal;
1994 a.s = NULL;
1995 set(&a);
1998 static gboolean
1999 browser_settings(const Arg *arg) {
2000 char line[255];
2001 if (!arg->s) {
2002 set_error("Missing argument.");
2003 return FALSE;
2005 strncpy(line, arg->s, 254);
2006 if (process_set_line(line))
2007 return TRUE;
2008 else {
2009 set_error("Invalid setting.");
2010 return FALSE;
2014 char *
2015 search_word(int whichword) {
2016 int k = 0;
2017 static char word[240];
2018 char *c = my_pair.line;
2020 while (isspace(*c) && *c)
2021 c++;
2023 switch (whichword) {
2024 case 0:
2025 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2026 word[k++] = *c;
2027 c++;
2029 word[k] = '\0';
2030 strncpy(my_pair.what, word, 20);
2031 break;
2032 case 1:
2033 while (*c && k < 240) {
2034 word[k++] = *c;
2035 c++;
2037 word[k] = '\0';
2038 strncpy(my_pair.value, word, 240);
2039 break;
2042 return c;
2045 static gboolean
2046 process_set_line(char *line) {
2047 char *c;
2048 int listlen, i;
2049 gboolean boolval;
2050 WebKitWebSettings *settings;
2052 settings = webkit_web_view_get_settings(client.gui.webview);
2053 my_pair.line = line;
2054 c = search_word(0);
2055 if (!strlen(my_pair.what))
2056 return FALSE;
2058 while (isspace(*c) && *c)
2059 c++;
2061 if (*c == ':' || *c == '=')
2062 c++;
2064 my_pair.line = c;
2065 c = search_word(1);
2067 listlen = LENGTH(browsersettings);
2068 for (i = 0; i < listlen; i++) {
2069 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2070 /* mandatory argument not provided */
2071 if (strlen(my_pair.value) == 0)
2072 return FALSE;
2073 /* process qmark? */
2074 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2075 return (process_save_qmark(my_pair.value, client.gui.webview));
2077 /* interpret boolean values */
2078 if (browsersettings[i].boolval) {
2079 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) {
2080 boolval = TRUE;
2081 } 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) {
2082 boolval = FALSE;
2083 } else {
2084 return FALSE;
2086 } else if (browsersettings[i].colourval) {
2087 /* interpret as hexadecimal colour */
2088 if (!parse_colour(my_pair.value)) {
2089 return FALSE;
2092 if (browsersettings[i].var != NULL) {
2093 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2094 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2095 /* in this case, \0 will not have been copied */
2096 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2097 /* in case this string is also used for a webkit setting, make sure it's consistent */
2098 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2099 echo_message(Info, "String too long; automatically truncated!");
2102 if (strlen(browsersettings[i].webkit) > 0) {
2103 /* activate appropriate webkit setting */
2104 if (browsersettings[i].boolval) {
2105 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2106 } else if (browsersettings[i].intval) {
2107 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2108 } else {
2109 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2111 webkit_web_view_set_settings(client.gui.webview, settings);
2114 if (strlen(my_pair.what) == 14) {
2115 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2116 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2117 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2118 complete_case_sensitive = boolval;
2120 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2121 toggle_proxy(boolval);
2122 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2123 toggle_scrollbars(boolval);
2124 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2125 gtk_widget_set_visible(GTK_WIDGET(client.gui.statusbar), boolval);
2126 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2127 gtk_widget_set_visible(client.gui.inputbox, boolval);
2128 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2129 escape_input_on_load = boolval;
2132 /* SSL certificate checking */
2133 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2134 if (boolval) {
2135 strict_ssl = TRUE;
2136 g_object_set(G_OBJECT(client.net.session), "ssl-strict", TRUE, NULL);
2137 } else {
2138 strict_ssl = FALSE;
2139 g_object_set(G_OBJECT(client.net.session), "ssl-strict", FALSE, NULL);
2142 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2143 g_object_set(G_OBJECT(client.net.session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2145 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2146 set_default_winsize(my_pair.value);
2149 /* reload page? */
2150 if (browsersettings[i].reload)
2151 webkit_web_view_reload(client.gui.webview);
2152 return TRUE;
2155 return FALSE;
2158 gboolean
2159 process_line(char *line) {
2160 char *c = line, *command_hist;
2161 int i;
2162 size_t len, length = strlen(line);
2163 gboolean found = FALSE, success = FALSE;
2164 Arg a;
2165 GList *l;
2167 while (isspace(*c))
2168 c++;
2169 /* Ignore blank lines. */
2170 if (c[0] == '\0')
2171 return TRUE;
2173 command_hist = g_strdup(c);
2175 /* check for colon command aliases first */
2176 for (l = client.config.colon_aliases; l; l = g_list_next(l)) {
2177 Alias *alias = (Alias *)l->data;
2178 if (length == strlen(alias->alias) && strncmp(alias->alias, line, length) == 0) {
2179 /* reroute to target command */
2180 c = alias->target;
2181 length = strlen(alias->target);
2182 break;
2186 /* check standard commands */
2187 for (i = 0; i < LENGTH(commands); i++) {
2188 if (commands[i].cmd == NULL)
2189 break;
2190 len = strlen(commands[i].cmd);
2191 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2192 found = TRUE;
2193 a.i = commands[i].arg.i;
2194 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2195 success = commands[i].func(&a);
2196 g_free(a.s);
2197 break;
2201 save_command_history(command_hist);
2202 g_free(command_hist);
2204 if (!found) {
2205 echo_message(Error, "Not a browser command: %s", c);
2206 } else if (!success) {
2207 if (client.state.error_msg != NULL) {
2208 echo_message(Error, client.state.error_msg);
2209 g_free(client.state.error_msg);
2210 client.state.error_msg = NULL;
2211 } else {
2212 echo_message(Error, "Unknown error. Please file a bug report!");
2215 return success;
2218 static gboolean
2219 search_tag(const Arg * a) {
2220 FILE *f;
2221 const char *filename;
2222 const char *tag = a->s;
2223 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2224 int t, i, intag, k;
2226 if (!tag) {
2227 /* The user must give us something to load up. */
2228 set_error("Bookmark tag required with this option.");
2229 return FALSE;
2232 if (strlen(tag) > MAXTAGSIZE) {
2233 set_error("Tag too long.");
2234 return FALSE;
2237 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2238 f = fopen(filename, "r");
2239 g_free((gpointer *)filename);
2240 if (f == NULL) {
2241 set_error("Couldn't open bookmarks file.");
2242 return FALSE;
2244 while (fgets(s, BUFFERSIZE-1, f)) {
2245 intag = 0;
2246 t = strlen(s) - 1;
2247 while (isspace(s[t]))
2248 t--;
2249 if (s[t] != ']') continue;
2250 while (t > 0) {
2251 if (s[t] == ']') {
2252 if (!intag)
2253 intag = t;
2254 else
2255 intag = 0;
2256 } else {
2257 if (s[t] == '[') {
2258 if (intag) {
2259 i = 0;
2260 k = t + 1;
2261 while (k < intag)
2262 foundtag[i++] = s[k++];
2263 foundtag[i] = '\0';
2264 /* foundtag now contains the tag */
2265 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2266 i = 0;
2267 while (isspace(s[i])) i++;
2268 k = 0;
2269 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2270 url[k] = '\0';
2271 Arg x = { .i = TargetNew, .s = url };
2272 open_arg(&x);
2275 intag = 0;
2278 t--;
2281 return TRUE;
2284 void
2285 toggle_proxy(gboolean onoff) {
2286 SoupURI *proxy_uri;
2287 char *filename, *new;
2289 if (onoff == FALSE) {
2290 g_object_set(client.net.session, "proxy-uri", NULL, NULL);
2291 } else {
2292 filename = (char *)g_getenv("http_proxy");
2294 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2295 * defined.
2297 if (filename == NULL)
2298 filename = (char *)g_getenv("HTTP_PROXY");
2300 if (filename != NULL && 0 < strlen(filename)) {
2301 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2302 proxy_uri = soup_uri_new(new);
2304 g_object_set(client.net.session, "proxy-uri", proxy_uri, NULL);
2306 soup_uri_free(proxy_uri);
2307 g_free(new);
2312 void
2313 toggle_scrollbars(gboolean onoff) {
2314 Gui *gui = &client.gui;
2315 if (onoff == TRUE) {
2316 gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2317 gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2318 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2319 } else {
2320 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2321 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2322 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2324 gtk_widget_set_scroll_adjustments (GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2326 return;
2329 void set_default_winsize(const char * const size) {
2330 char *p;
2331 int x = 640, y = 480;
2333 x = strtol(size, &p, 10);
2334 if (errno == ERANGE || x <= 0) {
2335 x = 640;
2336 goto out;
2339 if (p == size || strlen(size) == p - size)
2340 goto out;
2342 y = strtol(p + 1, NULL, 10);
2343 if (errno == ERANGE || y <= 0)
2344 y = 480;
2346 out:
2347 gtk_window_resize(GTK_WINDOW(client.gui.window), x, y);
2350 void
2351 update_url(const char *uri) {
2352 Gui *gui = &client.gui;
2353 gboolean ssl = g_str_has_prefix(uri, "https://");
2354 GdkColor color;
2355 WebKitWebFrame *frame;
2356 WebKitWebDataSource *src;
2357 WebKitNetworkRequest *request;
2358 SoupMessage *msg;
2359 gboolean ssl_ok;
2360 char *sslactivecolor;
2361 gchar *markup;
2362 #ifdef ENABLE_HISTORY_INDICATOR
2363 char before[] = " [";
2364 char after[] = "]";
2365 gboolean back = webkit_web_view_can_go_back(gui->webview);
2366 gboolean fwd = webkit_web_view_can_go_forward(gui->webview);
2368 if (!back && !fwd)
2369 before[0] = after[0] = '\0';
2370 #endif
2371 markup = g_markup_printf_escaped(
2372 #ifdef ENABLE_HISTORY_INDICATOR
2373 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2374 before, back ? "+" : "", fwd ? "-" : "", after
2375 #else
2376 "<span font=\"%s\">%s</span>", statusfont, uri
2377 #endif
2379 gtk_label_set_markup(GTK_LABEL(gui->status_url), markup);
2380 g_free(markup);
2381 if (ssl) {
2382 frame = webkit_web_view_get_main_frame(gui->webview);
2383 src = webkit_web_frame_get_data_source(frame);
2384 request = webkit_web_data_source_get_request(src);
2385 msg = webkit_network_request_get_message(request);
2386 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2387 if (ssl_ok)
2388 sslactivecolor = sslbgcolor;
2389 else
2390 sslactivecolor = sslinvalidbgcolor;
2392 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2393 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &color);
2394 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2395 gtk_widget_modify_fg(GTK_WIDGET(gui->status_url), GTK_STATE_NORMAL, &color);
2396 gtk_widget_modify_fg(GTK_WIDGET(gui->status_state), GTK_STATE_NORMAL, &color);
2399 void
2400 update_state() {
2401 State* state = &client.state;
2402 char *markup;
2403 int download_count = g_list_length(state->activeDownloads);
2404 GString *status = g_string_new("");
2406 /* construct the status line */
2408 /* count, modkey and input buffer */
2409 g_string_append_printf(status, "%.0d", state->count);
2410 if (state->current_modkey) g_string_append_c(status, state->current_modkey);
2412 /* the number of active downloads */
2413 if (state->activeDownloads) {
2414 g_string_append_printf(status, " %d active %s", download_count,
2415 (download_count == 1) ? "download" : "downloads");
2418 #ifdef ENABLE_WGET_PROGRESS_BAR
2419 /* the progressbar */
2421 int progress = -1;
2422 char progressbar[progressbartick + 1];
2424 if (state->activeDownloads) {
2425 progress = 0;
2426 GList *ptr;
2428 for (ptr = state->activeDownloads; ptr; ptr = g_list_next(ptr)) {
2429 progress += 100 * webkit_download_get_progress(ptr->data);
2432 progress /= download_count;
2434 } else if (webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FINISHED
2435 && webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FAILED) {
2437 progress = webkit_web_view_get_progress(client.gui.webview) * 100;
2440 if (progress >= 0) {
2441 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2442 g_string_append_printf(status, " %c%s%c",
2443 progressborderleft, progressbar, progressborderright);
2446 #endif
2448 /* and the current scroll position */
2450 int max = gtk_adjustment_get_upper(client.gui.adjust_v) - gtk_adjustment_get_page_size(client.gui.adjust_v);
2451 int val = (int)(gtk_adjustment_get_value(client.gui.adjust_v) / max * 100);
2453 if (max == 0)
2454 g_string_append(status, " All");
2455 else if (val == 0)
2456 g_string_append(status, " Top");
2457 else if (val == 100)
2458 g_string_append(status, " Bot");
2459 else
2460 g_string_append_printf(status, " %d%%", val);
2464 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2465 gtk_label_set_markup(GTK_LABEL(client.gui.status_state), markup);
2466 g_free(markup);
2468 g_string_free(status, TRUE);
2471 static void
2472 setup_client(void) {
2473 State *state = &client.state;
2474 Config *config = &client.config;
2476 state->mode = ModeNormal;
2477 state->count = 0;
2478 state->rememberedURI[0] = '\0';
2479 state->manual_focus = FALSE;
2480 state->commandhistory = NULL;
2481 state->commandpointer = 0;
2483 config->colon_aliases = NULL;
2484 config->cookie_timeout = 4800;
2487 void
2488 setup_modkeys() {
2489 unsigned int i;
2490 client.config.modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2491 char *ptr = client.config.modkeys;
2493 for (i = 0; i < LENGTH(keys); i++)
2494 if (keys[i].modkey && !strchr(client.config.modkeys, keys[i].modkey))
2495 *(ptr++) = keys[i].modkey;
2496 client.config.modkeys = realloc(client.config.modkeys, &ptr[0] - &client.config.modkeys[0] + 1);
2499 void
2500 setup_gui() {
2501 Gui *gui = &client.gui;
2502 State *state = &client.state;
2504 gui->scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2505 gui->scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2506 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2507 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2508 if (client.state.embed) {
2509 gui->window = GTK_WINDOW(gtk_plug_new(client.state.embed));
2510 } else {
2511 gui->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2512 gtk_window_set_wmclass(GTK_WINDOW(gui->window), "vimprobable2", "Vimprobable2");
2514 gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
2515 gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2516 gui->inputbox = gtk_entry_new();
2517 gui->webview = (WebKitWebView*)webkit_web_view_new();
2518 gui->statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2519 gui->eventbox = gtk_event_box_new();
2520 gui->status_url = gtk_label_new(NULL);
2521 gui->status_state = gtk_label_new(NULL);
2522 GdkColor bg;
2523 PangoFontDescription *font;
2524 GdkGeometry hints = { 1, 1 };
2525 gui->inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gui->webview));
2527 state->clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2528 state->clipboards[1] = gtk_clipboard_get(GDK_NONE);
2529 setup_settings();
2530 gdk_color_parse(statusbgcolor, &bg);
2531 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
2532 gtk_widget_set_name(GTK_WIDGET(gui->window), "Vimprobable2");
2533 gtk_window_set_geometry_hints(gui->window, NULL, &hints, GDK_HINT_MIN_SIZE);
2535 state->keymap = gdk_keymap_get_default();
2537 #ifdef DISABLE_SCROLLBAR
2538 gui->viewport = gtk_scrolled_window_new(NULL, NULL);
2539 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2540 #else
2541 /* Ensure we still see scrollbars. */
2542 gui->viewport = gtk_scrolled_window_new(gui->adjust_h, gui->adjust_v);
2543 #endif
2545 setup_signals();
2546 gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
2548 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2549 * titlebars, we can still scroll.
2551 gtk_widget_set_scroll_adjustments(GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2553 font = pango_font_description_from_string(urlboxfont[0]);
2554 gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
2555 pango_font_description_free(font);
2556 gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
2557 gtk_misc_set_alignment(GTK_MISC(gui->status_url), 0.0, 0.0);
2558 gtk_misc_set_alignment(GTK_MISC(gui->status_state), 1.0, 0.0);
2559 gtk_box_pack_start(gui->statusbar, gui->status_url, TRUE, TRUE, 2);
2560 gtk_box_pack_start(gui->statusbar, gui->status_state, FALSE, FALSE, 2);
2561 gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar));
2562 gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
2563 gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
2564 gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
2565 gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
2566 gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->box));
2567 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
2568 gtk_widget_show_all(GTK_WIDGET(gui->window));
2569 set_widget_font_and_color(gui->inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2570 g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2573 void
2574 setup_settings() {
2575 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2576 char *filename, *file_url;
2578 client.net.session = webkit_get_default_session();
2579 g_object_set(G_OBJECT(client.net.session), "ssl-ca-file", ca_bundle, NULL);
2580 g_object_set(G_OBJECT(client.net.session), "ssl-strict", strict_ssl, NULL);
2581 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2582 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2583 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2584 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2585 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2586 filename = g_strdup_printf(USER_STYLESHEET);
2587 file_url = g_strdup_printf("file://%s", filename);
2588 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2589 g_free(file_url);
2590 g_free(filename);
2591 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2592 g_object_get(G_OBJECT(settings), "zoom-step", &client.config.zoomstep, NULL);
2593 webkit_web_view_set_settings(client.gui.webview, settings);
2595 /* proxy */
2596 toggle_proxy(use_proxy);
2599 void
2600 setup_signals() {
2601 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
2602 #ifdef ENABLE_COOKIE_SUPPORT
2603 /* Headers. */
2604 g_signal_connect_after(G_OBJECT(client.net.session), "request-started", G_CALLBACK(new_generic_request), NULL);
2605 #endif
2606 /* Accept-language header */
2607 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2608 /* window */
2609 g_object_connect(G_OBJECT(client.gui.window),
2610 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2611 NULL);
2612 /* frame */
2613 g_signal_connect(G_OBJECT(frame),
2614 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2615 /* webview */
2616 g_object_connect(G_OBJECT(client.gui.webview),
2617 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2618 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2619 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2620 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2621 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2622 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2623 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2624 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2625 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2626 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2627 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2628 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2629 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2630 NULL);
2631 /* webview adjustment */
2632 g_object_connect(G_OBJECT(client.gui.adjust_v),
2633 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2634 NULL);
2635 /* inputbox */
2636 g_object_connect(G_OBJECT(client.gui.inputbox),
2637 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2638 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2639 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2640 #ifdef ENABLE_INCREMENTAL_SEARCH
2641 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2642 #endif
2643 NULL);
2644 /* inspector */
2645 g_signal_connect(G_OBJECT(client.gui.inspector),
2646 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2649 #ifdef ENABLE_USER_SCRIPTFILE
2650 static void
2651 scripts_run_user_file() {
2652 gchar *js = NULL, *user_scriptfile = NULL;
2653 GError *error = NULL;
2655 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2657 /* run the users script file */
2658 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2659 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2661 gchar *value = NULL, *message = NULL;
2663 jsapi_evaluate_script(js, &value, &message);
2664 g_free(js);
2665 if (message) {
2666 fprintf(stderr, "%s", message);
2668 g_free(value);
2669 g_free(message);
2670 } else {
2671 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2674 g_free(user_scriptfile);
2676 #endif
2678 #ifdef ENABLE_COOKIE_SUPPORT
2679 void
2680 setup_cookies()
2682 Network *net = &client.net;
2683 if (net->file_cookie_jar)
2684 g_object_unref(net->file_cookie_jar);
2686 if (net->session_cookie_jar)
2687 g_object_unref(net->session_cookie_jar);
2689 net->session_cookie_jar = soup_cookie_jar_new();
2691 net->cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2693 load_all_cookies();
2695 g_signal_connect(G_OBJECT(net->file_cookie_jar), "changed",
2696 G_CALLBACK(update_cookie_jar), NULL);
2699 /* TA: XXX - we should be using this callback for any header-requests we
2700 * receive (hence the name "new_generic_request" -- but for now, its use
2701 * is limited to handling cookies.
2703 void
2704 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2706 SoupMessageHeaders *soup_msg_h;
2707 SoupURI *uri;
2708 char *cookie_str;
2710 soup_msg_h = soup_msg->request_headers;
2711 soup_message_headers_remove(soup_msg_h, "Cookie");
2712 uri = soup_message_get_uri(soup_msg);
2713 if ((cookie_str = get_cookies(uri))) {
2714 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2715 g_free(cookie_str);
2718 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2720 return;
2723 char *
2724 get_cookies(SoupURI *soup_uri) {
2725 char *cookie_str;
2727 cookie_str = soup_cookie_jar_get_cookies(client.net.file_cookie_jar, soup_uri, TRUE);
2729 return cookie_str;
2732 void
2733 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2735 GSList *resp_cookie = NULL, *cookie_list;
2736 SoupCookie *cookie;
2738 cookie_list = soup_cookies_from_response(soup_msg);
2739 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
2741 SoupDate *soup_date;
2742 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2744 if (client.config.cookie_timeout && cookie->expires == NULL) {
2745 soup_date = soup_date_new_from_time_t(time(NULL) + client.config.cookie_timeout * 10);
2746 soup_cookie_set_expires(cookie, soup_date);
2747 soup_date_free(soup_date);
2749 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
2752 soup_cookies_free(cookie_list);
2754 return;
2757 void
2758 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2760 if (!new) {
2761 /* Nothing to do. */
2762 return;
2765 SoupCookie *copy;
2766 copy = soup_cookie_copy(new);
2768 soup_cookie_jar_add_cookie(client.net.session_cookie_jar, copy);
2770 return;
2773 void
2774 load_all_cookies(void)
2776 Network *net = &client.net;
2777 GSList *cookie_list;
2778 net->file_cookie_jar = soup_cookie_jar_text_new(net->cookie_store, COOKIES_STORAGE_READONLY);
2780 /* Put them back in the session store. */
2781 GSList *cookies_from_file = soup_cookie_jar_all_cookies(net->file_cookie_jar);
2782 cookie_list = cookies_from_file;
2784 for (; cookies_from_file;
2785 cookies_from_file = cookies_from_file->next)
2787 soup_cookie_jar_add_cookie(net->session_cookie_jar, cookies_from_file->data);
2790 soup_cookies_free(cookies_from_file);
2791 g_slist_free(cookie_list);
2793 return;
2796 #endif
2798 void
2799 mop_up(void) {
2800 /* Free up any nasty globals before exiting. */
2801 #ifdef ENABLE_COOKIE_SUPPORT
2802 if (client.net.cookie_store)
2803 g_free(client.net.cookie_store);
2804 #endif
2805 return;
2809 main(int argc, char *argv[]) {
2810 static Arg a;
2811 static char url[256] = "";
2812 static gboolean ver = false;
2813 static gboolean configfile_exists = FALSE;
2814 static const char *cfile = NULL;
2815 static gchar *winid = NULL;
2816 static GOptionEntry opts[] = {
2817 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2818 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2819 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2820 { NULL }
2822 static GError *err;
2823 args = argv;
2824 Config *config = &client.config;
2826 /* command line argument: version */
2827 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2828 g_printerr("can't init gtk: %s\n", err->message);
2829 g_error_free(err);
2830 return EXIT_FAILURE;
2833 if (ver) {
2834 printf("%s\n", INTERNAL_VERSION);
2835 return EXIT_SUCCESS;
2838 setup_client();
2840 if (getenv("TMPDIR")) {
2841 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
2842 temp_dir[MAX_SETTING_SIZE-1] = 0;
2845 if( getenv("XDG_CONFIG_HOME") )
2846 config->config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
2847 else
2848 config->config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
2850 if (cfile)
2851 config->configfile = g_strdup(cfile);
2852 else
2853 config->configfile = g_strdup_printf(RCFILE);
2855 if (!g_thread_supported())
2856 g_thread_init(NULL);
2858 if (winid) {
2859 if (strncmp(winid, "0x", 2) == 0) {
2860 client.state.embed = strtol(winid, NULL, 16);
2861 } else {
2862 client.state.embed = atoi(winid);
2866 setup_modkeys();
2867 make_keyslist();
2868 setup_gui();
2869 #ifdef ENABLE_COOKIE_SUPPORT
2870 setup_cookies();
2871 #endif
2873 make_searchengines_list(searchengines, LENGTH(searchengines));
2874 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
2876 /* Check if the specified file exists. */
2877 /* And only warn the user, if they explicitly asked for a config on the
2878 * command line.
2880 if (!(access(config->configfile, F_OK) == 0) && cfile) {
2881 echo_message(Info, "Config file '%s' doesn't exist", cfile);
2882 } else if ((access(config->configfile, F_OK) == 0))
2883 configfile_exists = true;
2885 /* read config file */
2886 /* But only report errors if we failed, and the file existed. */
2887 if ((SUCCESS != read_rcfile(config->configfile)) && configfile_exists) {
2888 echo_message(Error, "Error in config file '%s'", config->configfile);
2889 g_free(config->configfile);
2892 /* command line argument: URL */
2893 if (argc > 1) {
2894 strncpy(url, argv[argc - 1], 255);
2895 } else {
2896 strncpy(url, startpage, 255);
2899 a.i = TargetCurrent;
2900 a.s = url;
2901 open_arg(&a);
2902 gtk_main();
2904 mop_up();
2906 return EXIT_SUCCESS;