quickmarks extension to letters as well
[vimprobable.git] / main.c
blobbd3bcea4263db8c9f0bd8a21ef43370b957e3469
1 /*
2 (c) 2009 by Leon Winter
3 (c) 2009-2015 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-2014 by Daniel Carl
9 (c) 2012-2014 by Matthew Carter
10 (c) 2014 by Morgan Howe
11 (c) 2014 by Jan Niemeyer
12 see LICENSE file
15 #include <X11/Xlib.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <errno.h>
20 #include <stdlib.h>
21 #include "includes.h"
22 #include "vimprobable.h"
23 #include "utilities.h"
24 #include "callbacks.h"
25 #include "javascript.h"
27 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
28 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
29 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
31 /* remove unused bits, numlock symbol and buttons from keymask */
32 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
34 #define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
35 #define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
36 (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
38 /* callbacks here */
39 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
40 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
41 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
42 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
43 static WebKitWebView* inspector_new_cb(WebKitWebInspector* inspector, WebKitWebView* web_view);
44 static gboolean inspector_show_cb(WebKitWebInspector *inspector);
45 static gboolean inspector_close_cb(WebKitWebInspector *inspector);
46 static void inspector_finished_cb(WebKitWebInspector *inspector);
47 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
48 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
49 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
50 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
51 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
52 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
53 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
54 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
55 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
56 static void webview_open_js_window_cb(WebKitWebView *temp_view, WebKitWebFrame *frame,
57 WebKitNetworkRequest *request, WebKitWebNavigationAction *action,
58 WebKitWebPolicyDecision *policy, gpointer data);
59 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
60 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
61 static WebKitWebView* webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
62 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
63 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
64 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
65 static gboolean blank_cb(void);
67 /* functions */
68 static gboolean bookmark(const Arg *arg);
69 static gboolean browser_settings(const Arg *arg);
70 static gboolean commandhistoryfetch(const Arg *arg);
71 static gboolean complete(const Arg *arg);
72 static gboolean descend(const Arg *arg);
73 gboolean echo(const Arg *arg);
74 static gboolean focus_input(const Arg *arg);
75 static gboolean open_editor(const Arg *arg);
76 static gboolean edit_source(const Arg *arg);
77 void _resume_from_editor(GPid child_pid, int status, gpointer data);
78 void _resume_from_edit_source(GPid child_pid, int status, gpointer data);
79 static gboolean input(const Arg *arg);
80 static gboolean open_inspector(const Arg * arg);
81 static gboolean navigate(const Arg *arg);
82 static gboolean number(const Arg *arg);
83 static gboolean open_arg(const Arg *arg);
84 static gboolean open_remembered(const Arg *arg);
85 static gboolean paste(const Arg *arg);
86 static gboolean quickmark(const Arg *arg);
87 static gboolean quit(const Arg *arg);
88 static gboolean revive(const Arg *arg);
89 static gboolean print_frame(const Arg *arg);
90 static gboolean search(const Arg *arg);
91 static gboolean set(const Arg *arg);
92 static gboolean script(const Arg *arg);
93 static gboolean scroll(const Arg *arg);
94 static gboolean search_tag(const Arg *arg);
95 static gboolean yank(const Arg *arg);
96 static gboolean view_source(const Arg * arg);
97 static gboolean zoom(const Arg *arg);
98 static gboolean fake_key_event(const Arg *arg);
100 static void clear_focus(void);
101 static void update_url(const char *uri);
102 static void setup_client(void);
103 static void setup_modkeys(void);
104 static void setup_gui(void);
105 static void setup_settings(void);
106 static void setup_signals(void);
107 static void ascii_bar(int total, int state, char *string);
108 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
109 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
110 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
111 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
112 const char *bg_color_str, const char *fg_color_str);
113 static void scripts_run_user_file(void);
114 static void show_link(const char *link);
116 static gboolean history(void);
117 static gboolean process_set_line(char *line);
118 void save_command_history(char *line);
119 void toggle_proxy(gboolean onoff);
120 void toggle_scrollbars(gboolean onoff);
121 void set_default_winsize(const char * const size);
123 gboolean process_keypress(GdkEventKey *event);
124 void fill_suggline(char * suggline, const char * command, const char *fill_with);
125 GtkWidget * fill_eventbox(const char * completion_line);
126 static void mop_up(void);
128 #include "main.h"
130 /* variables */
131 static char **args;
133 #include "config.h"
134 #include "keymap.h"
136 /* Cookie support. */
137 #ifdef ENABLE_COOKIE_SUPPORT
138 static void setup_cookies(void);
139 static char *get_cookies(SoupURI *soup_uri);
140 static void load_all_cookies(void);
141 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
142 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
143 static void handle_response_headers(SoupMessage *soup_msg, gpointer unused);
144 #endif
146 Client client;
148 /* callbacks */
149 void
150 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
151 quit(NULL);
154 void
155 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
156 gtk_window_set_title(client.gui.window, title);
159 void
160 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
161 #ifdef ENABLE_GTK_PROGRESS_BAR
162 gtk_entry_set_progress_fraction(GTK_ENTRY(client.gui.inputbox), progress == 100 ? 0 : (double)progress/100);
163 #endif
164 update_state();
167 #ifdef ENABLE_WGET_PROGRESS_BAR
168 void
169 ascii_bar(int total, int state, char *string) {
170 int i;
172 for (i = 0; i < state; i++)
173 string[i] = progressbartickchar;
174 string[i++] = progressbarcurrent;
175 for (; i < total; i++)
176 string[i] = progressbarspacer;
177 string[i] = '\0';
179 #endif
181 void
182 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
183 Arg a = { .i = Silent, .s = g_strdup(JS_SETUP_HINTS) };
184 const char *uri = webkit_web_view_get_uri(webview);
186 update_url(uri);
187 script(&a);
188 g_free(a.s);
189 scripts_run_user_file();
191 if (client.state.mode == ModeInsert || client.state.mode == ModeHints) {
192 Arg a = { .i = ModeNormal };
193 set(&a);
195 client.state.manual_focus = FALSE;
198 void
199 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
200 WebKitWebSettings *settings = webkit_web_view_get_settings(webview);
201 gboolean scripts;
203 g_object_get(settings, "enable-scripts", &scripts, NULL);
204 if (escape_input_on_load && scripts && !client.state.manual_focus && !gtk_widget_is_focus(client.gui.inputbox)) {
205 clear_focus();
207 if (HISTORY_MAX_ENTRIES > 0)
208 history();
209 update_state();
212 void
213 webview_open_js_window_cb(WebKitWebView *temp_view, WebKitWebFrame *frame,
214 WebKitNetworkRequest *request, WebKitWebNavigationAction *action,
215 WebKitWebPolicyDecision *policy, gpointer data) {
216 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request)};
217 /* open the requested window */
218 open_arg(&a);
219 /* clean up */
220 gtk_widget_destroy(GTK_WIDGET(temp_view));
223 static WebKitWebView *
224 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
225 /* create a temporary webview to execute the script in */
226 WebKitWebView *temp_view = WEBKIT_WEB_VIEW(webkit_web_view_new());
227 /* wait until the new webview receives its new URI */
228 g_signal_connect(temp_view, "navigation-policy-decision-requested", G_CALLBACK(webview_open_js_window_cb), NULL);
229 return temp_view;
232 gboolean
233 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
234 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
235 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
236 open_arg(&a);
237 webkit_web_policy_decision_ignore(decision);
238 return TRUE;
241 gboolean
242 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
243 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
244 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
245 if (SOUP_STATUS_IS_SUCCESSFUL(client.net.http_status)) {
246 webkit_web_policy_decision_download(decision);
247 return TRUE;
249 return FALSE;
250 } else {
251 return FALSE;
255 static WebKitWebView*
256 inspector_new_cb(WebKitWebInspector *inspector, WebKitWebView* web_view) {
257 return WEBKIT_WEB_VIEW(webkit_web_view_new());
260 static gboolean
261 inspector_show_cb(WebKitWebInspector *inspector) {
262 WebKitWebView *webview;
263 State *state = &client.state;
265 if (state->is_inspecting) {
266 return FALSE;
269 webview = webkit_web_inspector_get_web_view(inspector);
270 gtk_paned_pack2(GTK_PANED(client.gui.pane), GTK_WIDGET(webview), TRUE, TRUE);
271 gtk_widget_show(GTK_WIDGET(webview));
273 state->is_inspecting = TRUE;
275 return TRUE;
278 static gboolean
279 inspector_close_cb(WebKitWebInspector *inspector) {
280 GtkWidget *widget;
281 State *state = &client.state;
283 if (!state->is_inspecting) {
284 return FALSE;
286 widget = GTK_WIDGET(webkit_web_inspector_get_web_view(inspector));
287 gtk_widget_hide(widget);
288 gtk_widget_destroy(widget);
290 state->is_inspecting = FALSE;
292 return TRUE;
295 static void
296 inspector_finished_cb(WebKitWebInspector *inspector) {
297 g_free(inspector);
300 gboolean
301 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
302 const gchar *filename;
303 gchar *uri, *path;
304 uint32_t size;
305 WebKitDownloadStatus status;
307 filename = webkit_download_get_suggested_filename(download);
308 if (filename == NULL || strlen(filename) == 0) {
309 filename = "vimprobable_download";
311 path = g_build_filename(downloads_path, filename, NULL);
312 uri = g_strconcat("file://", path, NULL);
313 webkit_download_set_destination_uri(download, uri);
314 g_free(path);
315 g_free(uri);
316 size = (uint32_t)webkit_download_get_total_size(download);
317 if (size > 0)
318 echo_message(Info, "Download %s started (expected size: %u bytes)...", filename, size);
319 else
320 echo_message(Info, "Download %s started (unknown size)...", filename);
321 client.state.activeDownloads = g_list_prepend(client.state.activeDownloads, download);
322 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
323 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
324 status = webkit_download_get_status(download);
325 if (status == WEBKIT_DOWNLOAD_STATUS_CREATED)
326 webkit_download_start(download);
327 update_state();
328 return TRUE;
331 gboolean
332 blank_cb(void) {
333 return TRUE;
336 void
337 download_progress(WebKitDownload *d, GParamSpec *pspec) {
338 WebKitDownloadStatus status = webkit_download_get_status(d);
340 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
341 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
342 echo_message(Error, "Error while downloading %s", webkit_download_get_suggested_filename(d));
343 } else {
344 echo_message(Info, "Download %s finished", webkit_download_get_suggested_filename(d));
346 client.state.activeDownloads = g_list_remove(client.state.activeDownloads, d);
348 update_state();
352 gboolean
353 process_keypress(GdkEventKey *event) {
354 State *state = &client.state;
355 KeyList *current;
356 guint keyval;
357 GdkModifierType irrelevant;
359 /* Get a mask of modifiers that shouldn't be considered for this event.
360 * E.g.: It shouldn't matter whether ';' is shifted or not. */
361 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
362 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
364 current = client.config.keylistroot;
366 while (current != NULL) {
367 if (current->Element.mask == (CLEAN(event->state) & ~irrelevant)
368 && (current->Element.modkey == state->current_modkey
369 || (!current->Element.modkey && !state->current_modkey)
370 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
371 && current->Element.key == keyval
372 && current->Element.func)
373 if (current->Element.func(&current->Element.arg)) {
374 state->current_modkey = state->count = 0;
375 update_state();
376 return TRUE;
378 current = current->next;
380 return FALSE;
383 gboolean
384 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
385 State *state = &client.state;
386 Arg a = { .i = ModeNormal, .s = NULL };
387 guint keyval;
388 GdkModifierType irrelevant;
390 /* Get a mask of modifiers that shouldn't be considered for this event.
391 * E.g.: It shouldn't matter whether ';' is shifted or not. */
392 gdk_keymap_translate_keyboard_state(state->keymap, event->hardware_keycode,
393 event->state, event->group, &keyval, NULL, NULL, &irrelevant);
395 switch (state->mode) {
396 case ModeNormal:
397 if ((CLEAN(event->state) & ~irrelevant) == 0) {
398 if (IS_ESCAPE(event)) {
399 echo_message(Info, "");
400 g_free(a.s);
401 } else if (state->current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
402 || (event->keyval == GDK_0 && state->count))) {
403 state->count = (state->count ? state->count * 10 : 0) + (event->keyval - GDK_0);
404 update_state();
405 return TRUE;
406 } else if (strchr(client.config.modkeys, event->keyval) && state->current_modkey != event->keyval) {
407 state->current_modkey = event->keyval;
408 update_state();
409 return TRUE;
412 /* keybindings */
413 if (process_keypress(event) == TRUE) return TRUE;
415 break;
416 case ModeInsert:
417 if (IS_ESCAPE(event)) {
418 a.i = Silent;
419 a.s = g_strdup("hints.clearFocus();");
420 script(&a);
421 g_free(a.s);
422 a.i = ModeNormal;
423 return set(&a);
424 } else if (CLEAN(event->state) & GDK_CONTROL_MASK) {
425 /* keybindings of non-printable characters */
426 if (process_keypress(event) == TRUE) return TRUE;
428 case ModePassThrough:
429 if (IS_ESCAPE(event)) {
430 echo_message(Info, "");
431 set(&a);
432 return TRUE;
434 break;
435 case ModeSendKey:
436 echo_message(Info, "");
437 set(&a);
438 break;
440 return FALSE;
443 void
444 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
445 const char *fg_color_str) {
446 GdkColor fg_color;
447 GdkColor bg_color;
448 PangoFontDescription *font;
450 font = pango_font_description_from_string(font_str);
451 gtk_widget_modify_font(widget, font);
452 pango_font_description_free(font);
454 if (fg_color_str)
455 gdk_color_parse(fg_color_str, &fg_color);
456 if (bg_color_str)
457 gdk_color_parse(bg_color_str, &bg_color);
459 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
460 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
462 return;
465 static void
466 show_link(const char *link) {
467 char *markup;
469 markup = g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link);
470 gtk_label_set_markup(GTK_LABEL(client.gui.status_url), markup);
471 strncpy(client.state.rememberedURI, link, BUF_SIZE);
472 g_free(markup);
475 void
476 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
477 const char *uri = webkit_web_view_get_uri(webview);
479 memset(client.state.rememberedURI, 0, BUF_SIZE);
480 if (link)
481 show_link(link);
482 else
483 update_url(uri);
486 gboolean
487 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
488 Arg a;
490 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
491 if (gtk_window_has_toplevel_focus(client.gui.window)) {
492 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
493 a.i = ModeNormal;
494 return set(&a);
495 } else if (!strcmp(message, "insertmode_on")) {
496 a.i = ModeInsert;
497 return set(&a);
500 return FALSE;
503 void
504 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
505 Gui *gui = &client.gui;
506 State *state = &client.state;
507 char *text;
508 guint16 length = gtk_entry_get_text_length(entry);
509 Arg a;
510 gboolean forward = FALSE;
512 a.i = HideCompletion;
513 complete(&a);
514 if (length == 0)
515 return;
516 text = (char*)gtk_entry_get_text(entry);
518 /* move focus from inputbox to print potential messages that could not be
519 * printed as long as the inputbox is focused */
520 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
522 if (length > 1 && text[0] == ':') {
523 process_line((text + 1));
524 } else if (length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
525 webkit_web_view_unmark_text_matches(gui->webview);
526 #ifdef ENABLE_MATCH_HIGHLITING
527 webkit_web_view_mark_text_matches(gui->webview, &text[1], FALSE, 0);
528 webkit_web_view_set_highlight_text_matches(gui->webview, TRUE);
529 #endif
530 state->count = 0;
531 #ifndef ENABLE_INCREMENTAL_SEARCH
532 a.s =& text[1];
533 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
534 search(&a);
535 #else
536 state->search_direction = forward;
537 if (state->search_handle) {
538 g_free(state->search_handle);
540 state->search_handle = g_strdup(&text[1]);
541 #endif
542 } else if (text[0] == '.' || text[0] == ',' || text[0] == ';') {
543 a.i = Silent;
544 a.s = g_strdup_printf("hints.fire();");
545 script(&a);
546 g_free(a.s);
547 update_state();
548 } else
549 return;
550 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
553 gboolean
554 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
555 Arg a;
556 int numval;
557 State *state = &client.state;
559 if (state->mode == ModeHints) {
560 if (event->keyval == GDK_Tab) {
561 a.i = Silent;
562 a.s = g_strdup_printf("hints.focusNextHint();");
563 script(&a);
564 g_free(a.s);
565 update_state();
566 return TRUE;
568 if (event->keyval == GDK_ISO_Left_Tab) {
569 a.i = Silent;
570 a.s = g_strdup_printf("hints.focusPreviousHint();");
571 script(&a);
572 g_free(a.s);
573 update_state();
574 return TRUE;
576 if (event->keyval == GDK_Return) {
577 a.i = Silent;
578 a.s = g_strdup_printf("hints.fire();");
579 script(&a);
580 g_free(a.s);
581 update_state();
582 return TRUE;
585 switch (event->keyval) {
586 case GDK_bracketleft:
587 case GDK_Escape:
588 if (!IS_ESCAPE(event)) break;
589 a.i = HideCompletion;
590 complete(&a);
591 a.i = ModeNormal;
592 state->commandpointer = 0;
593 return set(&a);
594 break;
595 case GDK_Tab:
596 a.i = DirectionNext;
597 return complete(&a);
598 break;
599 case GDK_Up:
600 a.i = DirectionPrev;
601 return commandhistoryfetch(&a);
602 break;
603 case GDK_Down:
604 a.i = DirectionNext;
605 return commandhistoryfetch(&a);
606 break;
607 case GDK_ISO_Left_Tab:
608 a.i = DirectionPrev;
609 return complete(&a);
610 break;
613 if (state->mode == ModeHints) {
614 if ((CLEAN(event->state) & GDK_SHIFT_MASK) &&
615 (CLEAN(event->state) & GDK_CONTROL_MASK) &&
616 (event->keyval == GDK_BackSpace)) {
617 state->count /= 10;
618 a.i = Silent;
619 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
620 script(&a);
621 g_free(a.s);
622 update_state();
623 return TRUE;
626 numval = g_unichar_digit_value((gunichar) gdk_keyval_to_unicode(event->keyval));
627 if ((numval >= 1 && numval <= 9) || (numval == 0 && state->count)) {
628 /* allow a zero as non-first number */
629 state->count = (state->count ? state->count * 10 : 0) + numval;
630 a.i = Silent;
631 a.s = g_strdup_printf("hints.updateHints(%d);", state->count);
632 script(&a);
633 g_free(a.s);
634 update_state();
635 return TRUE;
639 return FALSE;
642 gboolean
643 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
644 int i;
645 WebKitHitTestResult *result;
646 WebKitHitTestResultContext context;
647 State *state = &client.state;
648 if (state->mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
649 /* handle mouse click events */
650 for (i = 0; i < LENGTH(mouse); i++) {
651 if (mouse[i].mask == CLEAN(event->button.state)
652 && (mouse[i].modkey == state->current_modkey
653 || (!mouse[i].modkey && !state->current_modkey)
654 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
655 && mouse[i].button == event->button.button
656 && mouse[i].func) {
657 if (mouse[i].func(&mouse[i].arg)) {
658 state->current_modkey = state->count = 0;
659 update_state();
660 return TRUE;
664 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
665 g_object_get(result, "context", &context, NULL);
666 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE) {
667 Arg a = { .i = ModeInsert };
668 set(&a);
669 state->manual_focus = TRUE;
671 } else if (state->mode == ModeInsert && event->type == GDK_BUTTON_RELEASE) {
672 result = webkit_web_view_get_hit_test_result(WEBKIT_WEB_VIEW(widget), (GdkEventButton*)event);
673 g_object_get(result, "context", &context, NULL);
674 if (!(context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)) {
675 Arg a = { .i = ModeNormal };
676 set(&a);
678 } else {
679 gchar *value = NULL, *message = NULL;
680 jsapi_evaluate_script("window.getSelection().focusNode", &value, &message);
681 if (value && !strcmp(value, "[object HTMLFormElement]")) {
682 Arg a = { .i = ModeInsert, .s = NULL };
683 set(&a);
684 state->manual_focus = TRUE;
686 g_free(value);
687 g_free(message);
689 return FALSE;
692 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
693 Arg a;
694 guint16 length = gtk_entry_get_text_length(entry);
696 if (!length) {
697 a.i = HideCompletion;
698 complete(&a);
699 a.i = ModeNormal;
700 return set(&a);
702 return FALSE;
705 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
706 Arg a;
707 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
708 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
709 gboolean forward = FALSE;
711 /* Update incremental search if the user changes the search text.
713 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
714 * from the user. But if the entry is focused and the text is set
715 * through gtk_entry_set_text() in some asyncrounous operation,
716 * I would consider that a bug.
719 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
720 webkit_web_view_unmark_text_matches(client.gui.webview);
721 webkit_web_view_search_text(client.gui.webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
722 return TRUE;
723 } else if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length >= 1 &&
724 (text[0] == '.' || text[0] == ',' || text[0] == ';')) {
725 a.i = Silent;
726 switch (text[0]) {
727 case '.':
728 a.s = g_strconcat("hints.createHints('", text + 1, "', 'f');", NULL);
729 break;
731 case ',':
732 a.s = g_strconcat("hints.createHints('", text + 1, "', 'F');", NULL);
733 break;
735 case ';':
736 a.s = NULL;
737 switch (text[1]) {
738 case 's':
739 a.s = g_strconcat("hints.createHints('", text + 2, "', 's');", NULL);
740 break;
741 case 'y':
742 a.s = g_strconcat("hints.createHints('", text + 2, "', 'y');", NULL);
743 break;
744 case 'o':
745 a.s = g_strconcat("hints.createHints('", text + 2, "', 'f');", NULL);
746 break;
747 case 't': case 'w':
748 a.s = g_strconcat("hints.createHints('", text + 2, "', 'F');", NULL);
749 break;
750 case 'O': case 'T': case 'W':
751 a.s = g_strconcat("hints.createHints('", text + 2, "', 'O');", NULL);
752 break;
753 case 'i':
754 a.s = g_strconcat("hints.createHints('", text + 2, "', 'i');", NULL);
755 break;
756 case 'I':
757 a.s = g_strconcat("hints.createHints('", text + 2, "', 'I');", NULL);
758 break;
759 case 'l':
760 a.s = g_strconcat("hints.createHints('", text + 2, "', 'l');", NULL);
761 break;
763 break;
765 client.state.count = 0;
766 if (a.s) {
767 script(&a);
768 g_free(a.s);
771 return TRUE;
772 } else if (length == 0) {
773 client.state.mode = ModeNormal;
774 a.i = Silent;
775 a.s = g_strdup("hints.clearHints();");
776 script(&a);
777 g_free(a.s);
778 client.state.count = 0;
779 update_state();
782 return FALSE;
785 /* funcs here */
787 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
788 memset(suggline, 0, 512);
789 strncpy(suggline, command, 512);
790 strncat(suggline, " ", 1);
791 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
794 GtkWidget * fill_eventbox(const char * completion_line) {
795 GtkBox * row;
796 GtkWidget *row_eventbox, *el;
797 GdkColor color;
798 char *markup, *markup_tmp;
800 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
801 row_eventbox = gtk_event_box_new();
802 gdk_color_parse(completionbgcolor[0], &color);
803 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
804 el = gtk_label_new(NULL);
805 markup_tmp = g_markup_escape_text(completion_line, strlen(completion_line));
806 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
807 markup_tmp, "</span>", NULL);
808 gtk_label_set_markup(GTK_LABEL(el), markup);
809 g_free(markup_tmp);
810 g_free(markup);
811 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
812 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
813 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
814 return row_eventbox;
817 gboolean
818 complete(const Arg *arg) {
819 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
820 size_t listlen, len, cmdlen;
821 int i, spacepos;
822 Listelement *elementlist = NULL, *elementpointer;
823 gboolean highlight = FALSE;
824 GtkBox *row;
825 GtkWidget *row_eventbox, *el;
826 GtkBox *_table;
827 GdkColor color;
828 static GtkWidget *table, *top_border;
829 static char *prefix;
830 static char **suggestions;
831 static GtkWidget **widgets;
832 static int n = 0, m, current = -1;
833 Gui *gui = &client.gui;
835 str = (char*)gtk_entry_get_text(GTK_ENTRY(gui->inputbox));
836 len = strlen(str);
838 /* Get the length of the list of commands for completion. We need this to
839 * malloc/realloc correctly.
841 listlen = LENGTH(commands);
843 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
844 return TRUE;
845 if (prefix) {
846 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
847 gdk_color_parse(completionbgcolor[0], &color);
848 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
849 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
850 if ((arg->i == DirectionNext && current == 0)
851 || (arg->i == DirectionPrev && current == n - 1))
852 current = -1;
853 } else {
854 free(widgets);
855 free(suggestions);
856 free(prefix);
857 gtk_widget_destroy(GTK_WIDGET(table));
858 gtk_widget_destroy(GTK_WIDGET(top_border));
859 table = NULL;
860 widgets = NULL;
861 suggestions = NULL;
862 prefix = NULL;
863 n = 0;
864 current = -1;
865 if (arg->i == HideCompletion)
866 return TRUE;
868 } else if (arg->i == HideCompletion)
869 return TRUE;
870 if (!widgets) {
871 prefix = g_strdup(str);
872 widgets = malloc(sizeof(GtkWidget*) * listlen);
873 suggestions = malloc(sizeof(char*) * listlen);
874 top_border = gtk_event_box_new();
875 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
876 gdk_color_parse(completioncolor[2], &color);
877 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
878 table = gtk_event_box_new();
879 gdk_color_parse(completionbgcolor[0], &color);
880 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
881 highlight = len > 1;
882 if (strchr(str, ' ') == NULL) {
883 /* command completion */
884 listlen = LENGTH(commands);
885 for (i = 0; i < listlen; i++) {
886 if (commands[i].cmd == NULL)
887 break;
888 cmdlen = strlen(commands[i].cmd);
889 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
890 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
891 if (highlight) {
892 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
893 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
894 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
895 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
897 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
898 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
899 row_eventbox = gtk_event_box_new();
900 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
901 el = gtk_label_new(NULL);
902 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
903 free(s);
904 gtk_label_set_markup(GTK_LABEL(el), markup);
905 g_free(markup);
906 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
907 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
908 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
909 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
910 suggestions[n] = commands[i].cmd;
911 widgets[n++] = row_eventbox;
914 } else {
915 entry = (char *)malloc(512 * sizeof(char));
916 if (entry == NULL) {
917 return FALSE;
919 memset(entry, 0, 512);
920 suggurls = malloc(sizeof(char*) * listlen);
921 if (suggurls == NULL) {
922 return FALSE;
924 spacepos = strcspn(str, " ");
925 searchfor = (str + spacepos + 1);
926 strncpy(command, (str + 1), spacepos - 1);
927 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
928 /* browser settings */
929 listlen = LENGTH(browsersettings);
930 for (i = 0; i < listlen; i++) {
931 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
932 /* match */
933 fill_suggline(suggline, command, browsersettings[i].name);
934 /* FIXME(HP): This memory is never freed */
935 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
936 strncpy(suggurls[n], suggline, 512);
937 suggestions[n] = suggurls[n];
938 row_eventbox = fill_eventbox(suggline);
939 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
940 widgets[n++] = row_eventbox;
944 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
945 /* completion on tags */
946 spacepos = strcspn(str, " ");
947 searchfor = (str + spacepos + 1);
948 elementlist = complete_list(searchfor, 1, elementlist);
949 } else {
950 /* URL completion: bookmarks */
951 elementlist = complete_list(searchfor, 0, elementlist);
952 m = count_list(elementlist);
953 if (m < MAX_LIST_SIZE) {
954 /* URL completion: history */
955 elementlist = complete_list(searchfor, 2, elementlist);
958 elementpointer = elementlist;
959 while (elementpointer != NULL) {
960 fill_suggline(suggline, command, elementpointer->element);
961 /* FIXME(HP): This memory is never freed */
962 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
963 strncpy(suggurls[n], suggline, 512);
964 suggestions[n] = suggurls[n];
965 row_eventbox = fill_eventbox(suggline);
966 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
967 widgets[n++] = row_eventbox;
968 elementpointer = elementpointer->next;
969 if (n >= MAX_LIST_SIZE)
970 break;
972 free_list(elementlist);
973 if (suggurls != NULL) {
974 free(suggurls);
975 suggurls = NULL;
977 if (entry != NULL) {
978 free(entry);
979 entry = NULL;
982 /* TA: FIXME - this needs rethinking entirely. */
984 GtkWidget **widgets_temp = realloc(widgets, sizeof(*widgets) * n);
985 if (widgets_temp == NULL && widgets == NULL) {
986 fprintf(stderr, "Couldn't realloc() widgets\n");
987 exit(1);
989 widgets = widgets_temp;
990 char **suggestions_temp = realloc(suggestions, sizeof(*suggestions) * n);
991 if (suggestions_temp == NULL && suggestions == NULL) {
992 fprintf(stderr, "Couldn't realloc() suggestions\n");
993 exit(1);
995 suggestions = suggestions_temp;
997 if (!n) {
998 gdk_color_parse(completionbgcolor[1], &color);
999 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
1000 el = gtk_label_new(NULL);
1001 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
1002 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
1003 gtk_label_set_markup(GTK_LABEL(el), markup);
1004 g_free(markup);
1005 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
1007 gtk_box_pack_start(gui->box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
1008 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
1009 gtk_box_pack_start(gui->box, GTK_WIDGET(table), FALSE, FALSE, 0);
1010 gtk_widget_show_all(GTK_WIDGET(table));
1011 gtk_widget_show_all(GTK_WIDGET(top_border));
1012 if (!n)
1013 return TRUE;
1014 current = arg->i == DirectionPrev ? n - 1 : 0;
1016 if (current != -1) {
1017 gdk_color_parse(completionbgcolor[2], &color);
1018 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
1019 s = g_strconcat(":", suggestions[current], NULL);
1020 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), s);
1021 g_free(s);
1022 } else
1023 gtk_entry_set_text(GTK_ENTRY(gui->inputbox), prefix);
1024 gtk_editable_set_position(GTK_EDITABLE(gui->inputbox), -1);
1025 return TRUE;
1028 gboolean
1029 descend(const Arg *arg) {
1030 char *source = (char*)webkit_web_view_get_uri(client.gui.webview), *p = &source[0], *new;
1031 int i, len;
1032 client.state.count = client.state.count ? client.state.count : 1;
1034 if (!source)
1035 return TRUE;
1036 if (arg->i == Rootdir) {
1037 for (i = 0; i < 3; i++) /* get to the third slash */
1038 if (!(p = strchr(++p, '/')))
1039 return TRUE; /* if we cannot find it quit */
1040 } else {
1041 len = strlen(source);
1042 if (!len) /* if string is empty quit */
1043 return TRUE;
1044 p = source + len; /* start at the end */
1045 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
1046 ++client.state.count;
1047 for (i = 0; i < client.state.count; i++)
1048 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
1049 if (p == source) /* if we reach the first char pointer quit */
1050 return TRUE;
1051 ++p; /* since we do p-- in the while, we are pointing at
1052 the char before the slash, so +1 */
1054 len = p - source + 1; /* new length = end - start + 1 */
1055 new = malloc(len + 1);
1056 memcpy(new, source, len);
1057 new[len] = '\0';
1058 webkit_web_view_load_uri(client.gui.webview, new);
1059 free(new);
1060 return TRUE;
1063 gboolean
1064 echo(const Arg *arg) {
1065 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
1067 if (index < Info || index > Error)
1068 return TRUE;
1070 if (!gtk_widget_is_focus(GTK_WIDGET(client.gui.inputbox))) {
1071 set_widget_font_and_color(client.gui.inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1072 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), !arg->s ? "" : arg->s);
1075 return TRUE;
1078 static gboolean
1079 open_inspector(const Arg * arg) {
1080 gboolean inspect_enabled;
1081 WebKitWebSettings *settings;
1082 State *state = &client.state;
1084 settings = webkit_web_view_get_settings(client.gui.webview);
1085 g_object_get(G_OBJECT(settings), "enable-developer-extras", &inspect_enabled, NULL);
1086 if (inspect_enabled) {
1087 if (state->is_inspecting) {
1088 webkit_web_inspector_close(client.gui.inspector);
1089 } else {
1090 webkit_web_inspector_show(client.gui.inspector);
1092 return TRUE;
1093 } else {
1094 echo_message(Error, "Webinspector is not enabled");
1095 return FALSE;
1099 gboolean
1100 input(const Arg *arg) {
1101 int pos = 0;
1102 client.state.count = 0;
1103 const char *url;
1104 int index = Info;
1105 Arg a;
1106 GtkWidget *inputbox = client.gui.inputbox;
1108 /* if inputbox hidden, show it again */
1109 if (!gtk_widget_get_visible(inputbox))
1110 gtk_widget_set_visible(inputbox, TRUE);
1112 update_state();
1114 /* Set the colour and font back to the default, so that we don't still
1115 * maintain a red colour from a warning from an end of search indicator,
1116 * etc.
1118 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1120 /* to avoid things like :open URL :open URL2 or :open :open URL */
1121 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1122 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1123 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(client.gui.webview)))
1124 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1126 gtk_widget_grab_focus(inputbox);
1127 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1129 if (arg->s[0] == '.' || arg->s[0] == ',' || arg->s[0] == ';') {
1130 client.state.mode = ModeHints;
1131 a.i = Silent;
1132 switch (arg->s[0]) {
1133 case '.':
1134 a.s = g_strdup("hints.createHints('', 'f');");
1135 break;
1137 case ',':
1138 a.s = g_strdup("hints.createHints('', 'F');");
1139 break;
1141 case ';':
1142 a.s = NULL;
1143 if (arg->s[1]) {
1144 switch (arg->s[1]) {
1145 case 's':
1146 a.s = g_strdup("hints.createHints('', 's');");
1147 break;
1148 case 'y':
1149 a.s = g_strdup("hints.createHints('', 'y');");
1150 break;
1151 case 'o':
1152 a.s = g_strdup("hints.createHints('', 'f');");
1153 break;
1154 case 't': case 'w':
1155 a.s = g_strdup("hints.createHints('', 'F');");
1156 break;
1157 case 'O': case 'T': case 'W':
1158 a.s = g_strdup("hints.createHints('', 'O');");
1159 break;
1160 case 'i':
1161 a.s = g_strdup("hints.createHints('', 'i');");
1162 break;
1163 case 'I':
1164 a.s = g_strdup("hints.createHints('', 'I');");
1165 break;
1166 case 'l':
1167 a.s = g_strdup("hints.createHints('', 'l');");
1168 break;
1171 break;
1173 client.state.count = 0;
1174 if (a.s) {
1175 script(&a);
1176 g_free(a.s);
1180 return TRUE;
1183 gboolean
1184 navigate(const Arg *arg) {
1185 if (arg->i & NavigationForwardBack)
1186 webkit_web_view_go_back_or_forward(client.gui.webview, (arg->i == NavigationBack ? -1 : 1) * (client.state.count ? client.state.count : 1));
1187 else if (arg->i & NavigationReloadActions)
1188 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(client.gui.webview);
1189 else
1190 webkit_web_view_stop_loading(client.gui.webview);
1191 return TRUE;
1194 gboolean
1195 number(const Arg *arg) {
1196 const char *source = webkit_web_view_get_uri(client.gui.webview);
1197 char *uri, *p, *new;
1198 int number, diff = (client.state.count ? client.state.count : 1) * (arg->i == Increment ? 1 : -1);
1200 if (!source)
1201 return TRUE;
1202 uri = g_strdup(source); /* copy string */
1203 p =& uri[0];
1204 while(*p != '\0') /* goto the end of the string */
1205 ++p;
1206 --p;
1207 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1208 --p;
1209 if (*(++p) == '\0') { /* if no numbers were found abort */
1210 free(uri);
1211 return TRUE;
1213 number = atoi(p) + diff; /* apply diff on number */
1214 *p = '\0';
1215 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1216 webkit_web_view_load_uri(client.gui.webview, new);
1217 g_free(new);
1218 free(uri);
1219 return TRUE;
1222 gboolean
1223 open_arg(const Arg *arg) {
1224 char *argv[64];
1225 char *s = arg->s, *p = NULL, *new;
1226 Arg a = { .i = NavigationReload };
1227 int len, space = 0;
1228 const char *search_uri;
1229 char *search_term;
1230 struct stat statbuf;
1232 if (client.state.embed) {
1233 gchar winid[64];
1234 snprintf(winid, LENGTH(winid), "%u", (gint)client.state.embed);
1235 argv[0] = *args;
1236 argv[1] = "-e";
1237 argv[2] = winid;
1238 argv[3] = arg->s;
1239 argv[4] = NULL;
1240 } else {
1241 argv[0] = *args;
1242 argv[1] = arg->s;
1243 argv[2] = NULL;
1246 if (!arg->s)
1247 navigate(&a);
1248 else if (arg->i == TargetCurrent) {
1249 while(*s == ' ') /* strip leading whitespace */
1250 ++s;
1251 p = (s + strlen(s) - 1);
1252 while(*p == ' ') /* strip trailing whitespace */
1253 --p;
1254 *(p + 1) = '\0';
1255 len = strlen(s);
1256 new = NULL;
1257 /* check for external handlers */
1258 if (open_handler(s))
1259 return TRUE;
1260 /* check for search engines */
1261 p = strchr(s, ' ');
1262 if (!p) {
1263 /* shortcut without search term */
1264 p = s;
1265 } else {
1266 /* search term given */
1267 *p = '\0';
1268 space = 1;
1270 search_uri = find_uri_for_searchengine(s);
1271 if (search_uri != NULL) {
1272 if (space > 0) {
1273 search_term = soup_uri_encode(p+1, "&");
1274 new = g_strdup_printf(search_uri, search_term);
1275 g_free(search_term);
1276 } else {
1277 if (!strstr(search_uri, "%s"))
1278 new = g_strdup(search_uri);
1279 else {
1280 /* the search engine definition expected an argument */
1281 new = g_strdup_printf(search_uri, "");
1285 if (space > 0)
1286 *p = ' ';
1287 if (!new) {
1288 if (len > 3 && strstr(s, "://")) { /* valid url? */
1289 p = new = g_malloc(len + 1);
1290 while(*s != '\0') { /* strip whitespaces */
1291 if (*s != ' ')
1292 *(p++) = *s;
1293 ++s;
1295 *p = '\0';
1296 } else if (!stat(s, &statbuf)) { /* prepend "file://" */
1297 char *rpath = realpath(s, NULL);
1298 if (rpath != NULL) {
1299 len = strlen(rpath);
1300 new = g_malloc(sizeof("file://") + len);
1301 sprintf(new, "file://%s", rpath);
1302 free(rpath);
1303 } else {
1304 new = g_malloc(sizeof("file://") + len);
1305 sprintf(new, "file://%s", s);
1307 } else if (space > 0 || !strchr(s, '.')) { /* whitespaces or no dot? */
1308 search_uri = find_uri_for_searchengine(defaultsearch);
1309 if (search_uri != NULL) {
1310 search_term = soup_uri_encode(s, "&");
1311 new = g_strdup_printf(search_uri, search_term);
1312 g_free(search_term);
1314 } else { /* prepend "http://" */
1315 new = g_malloc(sizeof("http://") + len);
1316 strcpy(new, "http://");
1317 memcpy(&new[sizeof("http://") - 1], s, len + 1);
1320 webkit_web_view_load_uri(client.gui.webview, new);
1321 g_free(new);
1322 } else
1323 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1324 return TRUE;
1327 gboolean
1328 open_remembered(const Arg *arg)
1330 Arg a = {arg->i, client.state.rememberedURI};
1332 if (strcmp(client.state.rememberedURI, "")) {
1333 open_arg(&a);
1335 return TRUE;
1338 gboolean
1339 yank(const Arg *arg) {
1340 const char *url, *content;
1342 if (arg->i & SourceSelection) {
1343 webkit_web_view_copy_clipboard(client.gui.webview);
1344 if (arg->i & ClipboardPrimary)
1345 content = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1346 if (!content && arg->i & ClipboardGTK)
1347 content = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1348 if (content) {
1349 echo_message(Info, "Yanked %s", content);
1350 g_free((gpointer *)content);
1352 } else {
1353 if (arg->i & SourceURL) {
1354 url = webkit_web_view_get_uri(client.gui.webview);
1355 } else {
1356 url = arg->s;
1358 if (!url)
1359 return TRUE;
1361 echo_message(Info, "Yanked %s", url);
1362 if (arg->i & ClipboardPrimary)
1363 gtk_clipboard_set_text(client.state.clipboards[0], url, -1);
1364 if (arg->i & ClipboardGTK)
1365 gtk_clipboard_set_text(client.state.clipboards[1], url, -1);
1367 return TRUE;
1370 gboolean
1371 paste(const Arg *arg) {
1372 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1374 /* If we're over a link, open it in a new target. */
1375 if (strlen(client.state.rememberedURI) > 0) {
1376 Arg new_target = { .i = TargetNew, .s = arg->s };
1377 open_arg(&new_target);
1378 return TRUE;
1381 if (arg->i & ClipboardPrimary)
1382 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[0]);
1383 if (!a.s && arg->i & ClipboardGTK)
1384 a.s = gtk_clipboard_wait_for_text(client.state.clipboards[1]);
1385 if (a.s) {
1386 open_arg(&a);
1387 g_free(a.s);
1389 return TRUE;
1392 gboolean
1393 quit(const Arg *arg) {
1394 FILE *f;
1395 const char *filename;
1396 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1397 if (uri != NULL) {
1398 /* write last URL into status file for recreation with "u" */
1399 filename = g_strdup_printf("%s", client.config.config_base);
1400 filename = g_strdup_printf(private_mode ? "/dev/null" : CLOSED_URL_FILENAME);
1401 f = fopen(filename, "w");
1402 g_free((gpointer *)filename);
1403 if (f != NULL) {
1404 fprintf(f, "%s", uri);
1405 fclose(f);
1408 gtk_main_quit();
1409 return TRUE;
1412 gboolean
1413 revive(const Arg *arg) {
1414 FILE *f;
1415 const char *filename;
1416 char buffer[512] = "";
1417 Arg a = { .i = TargetNew, .s = NULL };
1418 /* get the URL of the window which has been closed last */
1419 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1420 f = fopen(filename, "r");
1421 g_free((gpointer *)filename);
1422 if (f != NULL) {
1423 fgets(buffer, 512, f);
1424 fclose(f);
1426 if (strlen(buffer) > 0) {
1427 a.s = buffer;
1428 open_arg(&a);
1429 return TRUE;
1431 return FALSE;
1434 static
1435 gboolean print_frame(const Arg *arg)
1437 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1438 webkit_web_frame_print (frame);
1439 return TRUE;
1442 gboolean
1443 search(const Arg *arg) {
1444 State *state = &client.state;
1445 state->count = state->count ? state->count : 1;
1446 gboolean success, direction = arg->i & DirectionPrev;
1448 if (arg->s) {
1449 if (state->search_handle) {
1450 g_free(state->search_handle);
1452 state->search_handle = g_strdup(arg->s);
1454 if (!state->search_handle)
1455 return TRUE;
1456 if (arg->i & DirectionAbsolute)
1457 state->search_direction = direction;
1458 else
1459 direction ^= state->search_direction;
1460 do {
1461 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, FALSE);
1462 if (!success) {
1463 if (arg->i & Wrapping) {
1464 success = webkit_web_view_search_text(client.gui.webview, state->search_handle, arg->i & CaseSensitive, direction, TRUE);
1465 if (success) {
1466 echo_message(Warning, "search hit %s, continuing at %s",
1467 direction ? "BOTTOM" : "TOP",
1468 direction ? "TOP" : "BOTTOM");
1469 } else
1470 break;
1471 } else
1472 break;
1474 } while(--state->count);
1475 if (!success) {
1476 echo_message(Error, "Pattern not found: %s", state->search_handle);
1478 return TRUE;
1481 gboolean
1482 set(const Arg *arg) {
1483 switch (arg->i) {
1484 case ModeNormal:
1485 if (client.state.search_handle) {
1486 g_free(client.state.search_handle);
1487 client.state.search_handle = NULL;
1488 webkit_web_view_unmark_text_matches(client.gui.webview);
1490 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), "");
1491 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1492 break;
1493 case ModePassThrough:
1494 echo_message(Info | NoAutoHide, "-- PASS THROUGH --");
1495 break;
1496 case ModeSendKey:
1497 echo_message(Info | NoAutoHide, "-- PASS TROUGH (next) --");
1498 break;
1499 case ModeInsert: /* should not be called manually but automatically */
1500 /* make sure we leaf focus from inputbox to show the new mode */
1501 gtk_widget_grab_focus(GTK_WIDGET(client.gui.webview));
1502 echo_message(Info | NoAutoHide, "-- INSERT --");
1503 break;
1504 default:
1505 return TRUE;
1507 client.state.mode = arg->i;
1508 return TRUE;
1511 gchar*
1512 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1513 JSStringRef string_ref;
1514 gchar *string;
1515 size_t length;
1517 string_ref = JSValueToStringCopy(context, ref, NULL);
1518 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1519 string = g_new(gchar, length);
1520 JSStringGetUTF8CString(string_ref, string, length);
1521 JSStringRelease(string_ref);
1522 return string;
1525 void
1526 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1527 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
1528 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1529 JSStringRef str;
1530 JSValueRef val, exception;
1532 str = JSStringCreateWithUTF8CString(script);
1533 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1534 JSStringRelease(str);
1535 if (!val)
1536 *message = jsapi_ref_to_string(context, exception);
1537 else
1538 *value = jsapi_ref_to_string(context, val);
1541 gboolean
1542 quickmark(const Arg *a) {
1543 int i, b;
1544 b = (int) a->s[0];
1545 char *fn = g_strdup_printf(QUICKMARK_FILE);
1546 FILE *fp;
1547 fp = fopen(fn, "r");
1548 g_free(fn);
1549 fn = NULL;
1550 char buf[100];
1552 if (fp != NULL && b < 128) {
1553 for( i=0; i < b; ++i ) {
1554 if (feof(fp)) {
1555 break;
1557 fgets(buf, 100, fp);
1559 char *ptr = strrchr(buf, '\n');
1560 *ptr = '\0';
1561 if (strlen(buf)) {
1562 Arg x = { .s = buf, .i = a->i ? TargetNew : TargetCurrent };
1563 return open_arg(&x);
1564 } else {
1565 echo_message(Error, "Quickmark %c not defined", (char) b);
1566 return false;
1568 } else { return false; }
1571 gboolean
1572 script(const Arg *arg) {
1573 gchar *value = NULL, *message = NULL;
1574 char text[BUF_SIZE] = "";
1575 Arg a;
1576 WebKitNetworkRequest *request;
1577 WebKitDownload *download;
1579 if (!arg->s) {
1580 set_error("Missing argument.");
1581 return FALSE;
1583 jsapi_evaluate_script(arg->s, &value, &message);
1584 if (message) {
1585 set_error(message);
1586 g_free(value);
1587 g_free(message);
1588 return FALSE;
1590 g_free(message);
1591 if (arg->i != Silent && value) {
1592 echo_message(arg->i, value);
1594 /* switch mode according to scripts return value */
1595 if (value) {
1596 if (strncmp(value, "done;", 5) == 0) {
1597 a.i = ModeNormal;
1598 set(&a);
1599 } else if (strncmp(value, "insert;", 7) == 0) {
1600 a.i = ModeInsert;
1601 set(&a);
1602 client.state.manual_focus = TRUE;
1603 } else if (strncmp(value, "save;", 5) == 0) {
1604 /* forced download */
1605 a.i = ModeNormal;
1606 set(&a);
1607 request = webkit_network_request_new((value + 5));
1608 download = webkit_download_new(request);
1609 webview_download_cb(client.gui.webview, download, (gpointer *)NULL);
1610 } else if (strncmp(value, "yank;", 5) == 0) {
1611 /* yank link URL to clipboard */
1612 a.i = ModeNormal;
1613 set(&a);
1614 a.i = ClipboardPrimary | ClipboardGTK;
1615 a.s = (value + 5);
1616 yank(&a);
1617 } else if (strncmp(value, "colon;", 6) == 0) {
1618 /* use link URL for colon command */
1619 strncpy(text, (char *)gtk_entry_get_text(GTK_ENTRY(client.gui.inputbox)), 1023);
1620 a.i = ModeNormal;
1621 set(&a);
1622 switch (text[1]) {
1623 case 'O':
1624 a.s = g_strconcat(":open ", (value + 6), NULL);
1625 break;
1626 case 'T': case 'W':
1627 a.s = g_strconcat(":tabopen ", (value + 6), NULL);
1628 break;
1630 if (a.s) {
1631 input(&a);
1632 g_free(a.s);
1634 } else if (strncmp(value, "open;", 5) == 0 || strncmp(value, "tabopen;", 8) == 0) {
1635 /* TODO: open element */
1636 a.i = ModeNormal;
1637 set(&a);
1638 if (strncmp(value, "open;", 5) == 0)
1639 a.i = TargetCurrent;
1640 else
1641 a.i = TargetNew;
1642 a.s = (strchr(value, ';') + 1);
1643 open_arg(&a);
1644 } else if (strncmp(value, "show_link;", 10) == 0) {
1645 a.i = ModeNormal;
1646 set(&a);
1647 char *link = strchr(value, ';') + 1;
1648 if (link) {
1649 memset(client.state.rememberedURI, 0, BUF_SIZE);
1650 show_link(link);
1652 } else if (strncmp(value, "error;", 6) == 0) {
1653 a.i = Error;
1654 set(&a);
1657 g_free(value);
1658 return TRUE;
1661 gboolean
1662 scroll(const Arg *arg) {
1663 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? client.gui.adjust_h : client.gui.adjust_v;
1664 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1665 float val = gtk_adjustment_get_value(adjust) / max * 100;
1666 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1667 unsigned int count = client.state.count;
1669 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1670 if (arg->i & ScrollMove)
1671 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1672 direction * /* direction */
1673 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1674 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1675 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1676 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1677 else
1678 gtk_adjustment_set_value(adjust,
1679 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1680 update_state();
1682 return TRUE;
1685 gboolean
1686 zoom(const Arg *arg) {
1687 unsigned int count = client.state.count;
1689 webkit_web_view_set_full_content_zoom(client.gui.webview, (arg->i & ZoomFullContent) > 0);
1690 webkit_web_view_set_zoom_level(client.gui.webview, (arg->i & ZoomOut) ?
1691 webkit_web_view_get_zoom_level(client.gui.webview) +
1692 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * client.config.zoomstep) :
1693 (count ? (float)count / 100.0 : 1.0));
1694 return TRUE;
1697 gboolean
1698 fake_key_event(const Arg *a) {
1699 if(!client.state.embed) {
1700 return FALSE;
1702 Display *xdpy;
1703 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1704 echo_message(Error, "Couldn't find the XDisplay.");
1705 return FALSE;
1708 XKeyEvent xk;
1709 xk.display = xdpy;
1710 xk.subwindow = None;
1711 xk.time = CurrentTime;
1712 xk.same_screen = True;
1713 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1714 xk.window = client.state.embed;
1715 xk.state = a->i;
1717 if( ! a->s ) {
1718 echo_message(Error, "Zero pointer as argument! Check your config.h");
1719 return FALSE;
1722 KeySym keysym;
1723 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1724 echo_message(Error, "Couldn't translate %s to keysym", a->s );
1725 return FALSE;
1728 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1729 echo_message(Error, "Couldn't translate keysym to keycode");
1730 return FALSE;
1733 xk.type = KeyPress;
1734 if( !XSendEvent(xdpy, client.state.embed, True, KeyPressMask, (XEvent *)&xk) ) {
1735 echo_message(Error, "XSendEvent failed");
1736 return FALSE;
1738 XFlush(xdpy);
1740 return TRUE;
1743 gboolean
1744 commandhistoryfetch(const Arg *arg) {
1745 State *state = &client.state;
1746 const int length = g_list_length(client.state.commandhistory);
1747 gchar *input_message = NULL;
1749 if (length > 0) {
1750 if (arg->i == DirectionPrev) {
1751 state->commandpointer = (length + state->commandpointer - 1) % length;
1752 } else {
1753 state->commandpointer = (length + state->commandpointer + 1) % length;
1756 const char* command = (char *)g_list_nth_data(state->commandhistory, state->commandpointer);
1757 input_message = g_strconcat(":", command, NULL);
1758 gtk_entry_set_text(GTK_ENTRY(client.gui.inputbox), input_message);
1759 g_free(input_message);
1760 gtk_editable_set_position(GTK_EDITABLE(client.gui.inputbox), -1);
1761 return TRUE;
1764 return FALSE;
1767 gboolean
1768 bookmark(const Arg *arg) {
1769 FILE *f;
1770 const char *filename;
1771 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1772 const char *title = webkit_web_view_get_title(client.gui.webview);
1773 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1774 f = fopen(filename, "a");
1775 g_free((gpointer *)filename);
1776 if (uri == NULL || strlen(uri) == 0) {
1777 set_error("No URI found to bookmark.");
1778 return FALSE;
1780 if (f != NULL) {
1781 fprintf(f, "%s", uri);
1782 if (title != NULL) {
1783 fprintf(f, "%s", " ");
1784 fprintf(f, "%s", title);
1786 if (arg->s && strlen(arg->s)) {
1787 build_taglist(arg, f);
1789 fprintf(f, "%s", "\n");
1790 fclose(f);
1791 echo_message(Info, "Bookmark saved");
1792 return TRUE;
1793 } else {
1794 set_error("Bookmarks file not found.");
1795 return FALSE;
1799 gboolean
1800 history() {
1801 FILE *f;
1802 const char *filename;
1803 const char *uri = webkit_web_view_get_uri(client.gui.webview);
1804 const char *title = webkit_web_view_get_title(client.gui.webview);
1805 char *entry, buffer[512], *new;
1806 int n, i = 0;
1807 gboolean finished = FALSE;
1808 if (uri != NULL) {
1809 if (title != NULL) {
1810 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1811 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1812 } else {
1813 entry = malloc((strlen(uri) + 1) * sizeof(char));
1814 memset(entry, 0, strlen(uri) + 1);
1816 if (entry != NULL) {
1817 strncpy(entry, uri, strlen(uri));
1818 if (title != NULL) {
1819 strncat(entry, " ", 1);
1820 strncat(entry, title, strlen(title));
1822 n = strlen(entry);
1823 filename = g_strdup_printf(private_mode ? "/dev/null" : HISTORY_STORAGE_FILENAME);
1824 f = fopen(filename, "r");
1825 if (f != NULL) {
1826 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1827 if (new != NULL) {
1828 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1829 /* newest entries go on top */
1830 strncpy(new, entry, strlen(entry));
1831 strncat(new, "\n", 1);
1832 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1833 while (finished != TRUE) {
1834 if ((char *)NULL == fgets(buffer, 512, f)) {
1835 /* check if end of file was reached / error occured */
1836 if (!feof(f)) {
1837 break;
1839 /* end of file reached */
1840 finished = TRUE;
1841 continue;
1843 /* compare line (-1 because of newline character) */
1844 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1845 /* if the URI is already in history; we put it on top and skip it here */
1846 strncat(new, buffer, 512);
1847 i++;
1849 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1850 break;
1853 fclose(f);
1855 f = fopen(filename, "w");
1856 g_free((gpointer *)filename);
1857 if (f != NULL) {
1858 fprintf(f, "%s", new);
1859 fclose(f);
1861 if (new != NULL) {
1862 free(new);
1863 new = NULL;
1867 if (entry != NULL) {
1868 free(entry);
1869 entry = NULL;
1872 return TRUE;
1875 static gboolean
1876 view_source(const Arg * arg) {
1877 gboolean current_mode = webkit_web_view_get_view_source_mode(client.gui.webview);
1878 webkit_web_view_set_view_source_mode(client.gui.webview, !current_mode);
1879 webkit_web_view_reload(client.gui.webview);
1880 return TRUE;
1883 /* open an external editor defined by the protocol handler for
1884 vimprobableedit on a text box or similar */
1885 static gboolean
1886 open_editor(const Arg *arg) {
1887 char *text = NULL;
1888 gboolean success;
1889 GPid child_pid;
1890 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1891 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1892 temp_dir);
1893 int temp_file_handle = -1;
1895 /* check if active element is suitable for text editing */
1896 jsapi_evaluate_script("document.activeElement.tagName", &value, &message);
1897 if (value == NULL) {
1898 g_free(message);
1899 return FALSE;
1901 tag = g_strdup(value);
1902 if (strcmp(tag, "INPUT") == 0) {
1903 /* extra check: type == text */
1904 jsapi_evaluate_script("document.activeElement.type", &value, &message);
1905 if (strcmp(value, "text") != 0) {
1906 g_free(value);
1907 g_free(message);
1908 return FALSE;
1910 g_free(value);
1911 g_free(message);
1912 } else if (strcmp(tag, "TEXTAREA") != 0) {
1913 g_free(value);
1914 g_free(message);
1915 return FALSE;
1917 jsapi_evaluate_script("document.activeElement.value", &value, &message);
1918 text = g_strdup(value);
1919 if (text == NULL) {
1920 g_free(value);
1921 g_free(message);
1922 return FALSE;
1924 jsapi_evaluate_script("editElem = document.activeElement", &value, &message);
1925 g_free(value);
1926 g_free(message);
1928 /* write text into temporary file */
1929 temp_file_handle = mkstemp(temp_file_name);
1930 if (temp_file_handle == -1) {
1931 message = g_strdup_printf("Could not create temporary file: %s",
1932 strerror(errno));
1933 echo_message(Error, message);
1934 g_free(value);
1935 g_free(message);
1936 g_free(text);
1937 return FALSE;
1939 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
1940 message = g_strdup_printf("Short write to temporary file: %s",
1941 strerror(errno));
1942 echo_message(Error, message);
1943 g_free(value);
1944 g_free(message);
1945 g_free(text);
1946 return FALSE;
1948 close(temp_file_handle);
1949 g_free(text);
1951 /* spawn editor */
1952 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
1953 success = open_handler_pid(edit_url, &child_pid);
1954 g_free(edit_url);
1955 if (!success) {
1956 echo_message(Error, "External editor open failed (no handler for"
1957 " vimprobableedit protocol?)");
1958 unlink(temp_file_name);
1959 g_free(value);
1960 g_free(message);
1961 return FALSE;
1964 /* mark the active text box as "under processing" */
1965 jsapi_evaluate_script(
1966 "editElem.disabled = true;"
1967 "editElem.originalBackground = "
1968 " editElem.style.background;"
1969 "editElem.style.background = '#aaaaaa';"
1970 ,&value, &message);
1972 g_child_watch_add(child_pid, _resume_from_editor, temp_file_name);
1974 /* temp_file_name is freed in _resume_from_editor */
1975 g_free(value);
1976 g_free(message);
1977 g_free(tag);
1978 return TRUE;
1981 /* open an external editor defined by the protocol handler for
1982 vimprobableedit on page source */
1983 static gboolean
1984 edit_source(const Arg *arg) {
1985 char *text = NULL;
1986 gboolean success;
1987 GPid child_pid;
1988 gchar *value = NULL, *message = NULL, *tag = NULL, *edit_url = NULL;
1989 gchar *temp_file_name = g_strdup_printf("%s/vimprobableeditXXXXXX",
1990 temp_dir);
1991 int temp_file_handle = -1;
1993 jsapi_evaluate_script("document.documentElement.innerHTML", &value, &message);
1994 text = g_strdup(value);
1995 if (text == NULL) {
1996 g_free(value);
1997 g_free(message);
1998 return FALSE;
2001 /* write text into temporary file */
2002 temp_file_handle = mkstemp(temp_file_name);
2003 if (temp_file_handle == -1) {
2004 message = g_strdup_printf("Could not create temporary file: %s",
2005 strerror(errno));
2006 echo_message(Error, message);
2007 g_free(value);
2008 g_free(message);
2009 g_free(text);
2010 return FALSE;
2012 if (write(temp_file_handle, text, strlen(text)) != strlen(text)) {
2013 message = g_strdup_printf("Short write to temporary file: %s",
2014 strerror(errno));
2015 echo_message(Error, message);
2016 g_free(value);
2017 g_free(message);
2018 g_free(text);
2019 return FALSE;
2021 close(temp_file_handle);
2022 g_free(text);
2024 /* spawn editor */
2025 edit_url = g_strdup_printf("vimprobableedit:%s", temp_file_name);
2026 success = open_handler_pid(edit_url, &child_pid);
2027 g_free(edit_url);
2028 if (!success) {
2029 echo_message(Error, "External editor open failed (no handler for"
2030 " vimprobableedit protocol?)");
2031 unlink(temp_file_name);
2032 g_free(value);
2033 g_free(message);
2034 return FALSE;
2037 g_child_watch_add(child_pid, _resume_from_edit_source, temp_file_name);
2039 /* temp_file_name is freed in _resume_from_editor */
2040 g_free(value);
2041 g_free(message);
2042 g_free(tag);
2043 return TRUE;
2047 /* pick up from where open_editor left the work to the glib event loop.
2049 This is called when the external editor exits.
2051 The data argument points to allocated memory containing the temporary file
2052 name. */
2053 void
2054 _resume_from_editor(GPid child_pid, int child_status, gpointer data) {
2055 FILE *fp;
2056 GString *set_value_js = g_string_new(
2057 "editElem.value = \"");
2058 g_spawn_close_pid(child_pid);
2059 gchar *value = NULL, *message = NULL;
2060 gchar *temp_file_name = data;
2061 gchar buffer[BUF_SIZE] = "";
2062 gchar *buf_ptr = buffer;
2063 int char_read;
2065 jsapi_evaluate_script(
2066 "editElem.disabled = true;"
2067 "editElem.style.background = '#aaaaaa';"
2068 ,&value, &message);
2069 g_free(value);
2070 g_free(message);
2072 if (child_status) {
2073 echo_message(Error, "External editor returned with non-zero status,"
2074 " discarding edits.");
2075 goto error_exit;
2078 /* re-read the new contents of the file and put it into the HTML element */
2079 if (!access(temp_file_name, R_OK) == 0) {
2080 message = g_strdup_printf("Could not access temporary file: %s",
2081 strerror(errno));
2082 goto error_exit;
2084 fp = fopen(temp_file_name, "r");
2085 if (fp == NULL) {
2086 /* this would be too weird to even emit an error message */
2087 goto error_exit;
2089 jsapi_evaluate_script("editElem.value = '';",
2090 &value, &message);
2091 g_free(value);
2092 g_free(message);
2094 while (EOF != (char_read = fgetc(fp))) {
2095 if (char_read == '\n') {
2096 *buf_ptr++ = '\\';
2097 *buf_ptr++ = 'n';
2098 } else if (char_read == '"') {
2099 *buf_ptr++ = '\\';
2100 *buf_ptr++ = '"';
2101 } else {
2102 *buf_ptr++ = char_read;
2104 /* ship out as the buffer when space gets tight. This has
2105 fuzz to save on thinking, plus we have enough space for the
2106 trailing "; in any case. */
2107 if (buf_ptr-buffer>=BUF_SIZE-10) {
2108 *buf_ptr = 0;
2109 g_string_append(set_value_js, buffer);
2110 buf_ptr = buffer;
2113 *buf_ptr++ = '"';
2114 *buf_ptr++ = ';';
2115 *buf_ptr = 0;
2116 g_string_append(set_value_js, buffer);
2117 fclose(fp);
2119 jsapi_evaluate_script(set_value_js->str, &value, &message);
2121 /* Fall through, error and normal exit are identical */
2122 error_exit:
2123 jsapi_evaluate_script(
2124 "editElem.disabled = false;"
2125 "editElem.style.background ="
2126 " editElem.originalBackground;"
2127 ,&value, &message);
2129 g_string_free(set_value_js, TRUE);
2130 unlink(temp_file_name);
2131 g_free(temp_file_name);
2132 g_free(value);
2133 g_free(message);
2136 /* pick up from where edit_source left the work to the glib event loop.
2138 This is called when the external editor exits.
2140 The data argument points to allocated memory containing the temporary file
2141 name. */
2142 void
2143 _resume_from_edit_source(GPid child_pid, int child_status, gpointer data) {
2144 FILE *fp;
2145 GString *set_value_js = g_string_new(
2146 "document.documentElement.innerHTML = \"");
2147 g_spawn_close_pid(child_pid);
2148 gchar *value = NULL, *message = NULL;
2149 gchar *temp_file_name = data;
2150 gchar buffer[BUF_SIZE] = "";
2151 gchar *buf_ptr = buffer;
2152 int char_read;
2154 if (child_status) {
2155 echo_message(Error, "External editor returned with non-zero status,"
2156 " discarding edits.");
2157 goto error_exit;
2160 /* re-read the new contents of the file and put it into the HTML element */
2161 if (!access(temp_file_name, R_OK) == 0) {
2162 message = g_strdup_printf("Could not access temporary file: %s",
2163 strerror(errno));
2164 goto error_exit;
2166 fp = fopen(temp_file_name, "r");
2167 if (fp == NULL) {
2168 /* this would be too weird to even emit an error message */
2169 goto error_exit;
2171 jsapi_evaluate_script("document.documentElement.innerHTML = '';",
2172 &value, &message);
2173 g_free(value);
2174 g_free(message);
2176 while (EOF != (char_read = fgetc(fp))) {
2177 if (char_read == '\n') {
2178 *buf_ptr++ = '\\';
2179 *buf_ptr++ = 'n';
2180 } else if (char_read == '"') {
2181 *buf_ptr++ = '\\';
2182 *buf_ptr++ = '"';
2183 } else {
2184 *buf_ptr++ = char_read;
2186 /* ship out as the buffer when space gets tight. This has
2187 fuzz to save on thinking, plus we have enough space for the
2188 trailing "; in any case. */
2189 if (buf_ptr-buffer>=BUF_SIZE-10) {
2190 *buf_ptr = 0;
2191 g_string_append(set_value_js, buffer);
2192 buf_ptr = buffer;
2195 *buf_ptr++ = '"';
2196 *buf_ptr++ = ';';
2197 *buf_ptr = 0;
2198 g_string_append(set_value_js, buffer);
2199 fclose(fp);
2201 jsapi_evaluate_script(set_value_js->str, &value, &message);
2203 error_exit:
2205 g_string_free(set_value_js, TRUE);
2206 unlink(temp_file_name);
2207 g_free(temp_file_name);
2208 g_free(value);
2209 g_free(message);
2212 static gboolean
2213 focus_input(const Arg *arg) {
2214 static Arg a;
2216 a.s = g_strdup("hints.focusInput();");
2217 a.i = Silent;
2218 script(&a);
2219 g_free(a.s);
2220 update_state();
2221 client.state.manual_focus = TRUE;
2222 return TRUE;
2225 static void
2226 clear_focus(void) {
2227 static Arg a;
2229 a.s = g_strdup("hints.clearFocus();");
2230 a.i = Silent;
2231 script(&a);
2232 g_free(a.s);
2233 a.i = ModeNormal;
2234 a.s = NULL;
2235 set(&a);
2238 static gboolean
2239 browser_settings(const Arg *arg) {
2240 char line[255];
2241 if (!arg->s) {
2242 set_error("Missing argument.");
2243 return FALSE;
2245 strncpy(line, arg->s, 254);
2246 if (process_set_line(line))
2247 return TRUE;
2248 else {
2249 set_error("Invalid setting.");
2250 return FALSE;
2254 char *
2255 search_word(int whichword) {
2256 int k = 0;
2257 static char word[240];
2258 char *c = my_pair.line;
2260 while (isspace(*c) && *c)
2261 c++;
2263 switch (whichword) {
2264 case 0:
2265 while (*c && !isspace (*c) && *c != '=' && k < 240) {
2266 word[k++] = *c;
2267 c++;
2269 word[k] = '\0';
2270 strncpy(my_pair.what, word, 20);
2271 break;
2272 case 1:
2273 while (*c && k < 240) {
2274 word[k++] = *c;
2275 c++;
2277 word[k] = '\0';
2278 strncpy(my_pair.value, word, 240);
2279 break;
2282 return c;
2285 static gboolean
2286 process_set_line(char *line) {
2287 char *c;
2288 int listlen, i;
2289 int intval;
2290 gboolean boolval;
2291 WebKitWebSettings *settings;
2293 settings = webkit_web_view_get_settings(client.gui.webview);
2294 my_pair.line = line;
2295 c = search_word(0);
2296 if (!strlen(my_pair.what))
2297 return FALSE;
2299 while (isspace(*c) && *c)
2300 c++;
2302 if (*c == ':' || *c == '=')
2303 c++;
2305 my_pair.line = c;
2306 c = search_word(1);
2308 listlen = LENGTH(browsersettings);
2309 for (i = 0; i < listlen; i++) {
2310 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
2311 /* mandatory argument not provided */
2312 if (strlen(my_pair.value) == 0)
2313 return FALSE;
2314 /* process qmark? */
2315 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
2316 return (process_save_qmark(my_pair.value, client.gui.webview));
2318 /* interpret boolean values */
2319 if (browsersettings[i].boolval) {
2320 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) {
2321 boolval = TRUE;
2322 } 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) {
2323 boolval = FALSE;
2324 } else {
2325 return FALSE;
2327 } else if (browsersettings[i].colourval) {
2328 /* interpret as hexadecimal colour */
2329 if (!parse_colour(my_pair.value)) {
2330 return FALSE;
2332 } else if (browsersettings[i].intval) {
2333 intval = atoi(my_pair.value);
2335 if (browsersettings[i].var != NULL) {
2336 strncpy(browsersettings[i].var, my_pair.value, MAX_SETTING_SIZE);
2337 if (strlen(my_pair.value) > MAX_SETTING_SIZE - 1) {
2338 /* in this case, \0 will not have been copied */
2339 browsersettings[i].var[MAX_SETTING_SIZE - 1] = '\0';
2340 /* in case this string is also used for a webkit setting, make sure it's consistent */
2341 my_pair.value[MAX_SETTING_SIZE - 1] = '\0';
2342 echo_message(Info, "String too long; automatically truncated!");
2345 if (strlen(browsersettings[i].webkit) > 0) {
2346 /* activate appropriate webkit setting */
2347 if (browsersettings[i].boolval) {
2348 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
2349 } else if (browsersettings[i].intval) {
2350 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
2351 } else {
2352 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
2354 webkit_web_view_set_settings(client.gui.webview, settings);
2357 if (strlen(my_pair.what) == 14) {
2358 if (strncmp("acceptlanguage", my_pair.what, 14) == 0) {
2359 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2360 } else if (strncmp("completioncase", my_pair.what, 14) == 0) {
2361 complete_case_sensitive = boolval;
2363 } else if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
2364 toggle_proxy(boolval);
2365 } else if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0) {
2366 toggle_scrollbars(boolval);
2367 } else if (strlen(my_pair.what) == 9 && strncmp("statusbar", my_pair.what, 9) == 0) {
2368 gtk_widget_set_visible(GTK_WIDGET(client.gui.statusbar), boolval);
2369 } else if (strlen(my_pair.what) == 8 && strncmp("inputbox", my_pair.what, 8) == 0) {
2370 gtk_widget_set_visible(client.gui.inputbox, boolval);
2371 } else if (strlen(my_pair.what) == 11 && strncmp("escapeinput", my_pair.what, 11) == 0) {
2372 escape_input_on_load = boolval;
2373 } else if (strlen(my_pair.what) == 7 && strncmp("private", my_pair.what, 7) == 0) {
2374 /* Store the state of the last 'on' cookie state before toggling it off */
2375 if (boolval) {
2376 /* Only update this LastOn state if private mode was false before */
2377 if (!private_mode) {
2378 CookiePolicyLastOn = CookiePolicy;
2380 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
2381 } else {
2382 /* If here, we are not in private_mode, so restore the cookie policy */
2383 CookiePolicy = CookiePolicyLastOn;
2385 soup_cookie_jar_set_accept_policy(client.net.session_cookie_jar, CookiePolicy);
2386 private_mode = boolval; /* Lastly, set the private_mode for use in other areas */
2387 } else if (strlen(my_pair.what) == 7 && strncmp("cookies", my_pair.what, 7) == 0) {
2388 /* cookie policy */
2389 if (strncmp(my_pair.value, "on", 2) == 0 || strncmp(my_pair.value, "true", 4) == 0 ||
2390 strncmp(my_pair.value, "ON", 2) == 0 || strncmp(my_pair.value, "TRUE", 4) == 0 ||
2391 strncmp(my_pair.value, "all", 3) == 0 || strncmp(my_pair.value, "ALL", 3) == 0) {
2392 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
2393 } else if (strncmp(my_pair.value, "off", 3) == 0 || strncmp(my_pair.value, "false", 5) == 0 ||
2394 strncmp(my_pair.value, "OFF", 3) == 0 || strncmp(my_pair.value, "FALSE", 5) == 0 ||
2395 strncmp(my_pair.value, "never", 5) == 0 || strncmp(my_pair.value, "NEVER", 5) == 5 ||
2396 strncmp(my_pair.value, "none", 4) == 0 || strncmp(my_pair.value, "NONE", 4) == 0) {
2397 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
2398 } else if (strncmp(my_pair.value, "origin", 6) == 0 || strncmp(my_pair.value, "ORIGIN", 6) == 0 ||
2399 strncmp(my_pair.value, "no_third", 8) == 0 || strncmp(my_pair.value, "NO_THIRD", 8) == 0 ||
2400 strncmp(my_pair.value, "no third", 8) == 0 || strncmp(my_pair.value, "NO THIRD", 8) == 0) {
2401 CookiePolicy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
2402 } else {
2403 return FALSE;
2405 soup_cookie_jar_set_accept_policy(client.net.session_cookie_jar, CookiePolicy);
2408 /* SSL certificate checking */
2409 if (strlen(my_pair.what) == 9 && strncmp("strictssl", my_pair.what, 9) == 0) {
2410 if (boolval) {
2411 strict_ssl = TRUE;
2412 g_object_set(G_OBJECT(client.net.session), "ssl-strict", TRUE, NULL);
2413 } else {
2414 strict_ssl = FALSE;
2415 g_object_set(G_OBJECT(client.net.session), "ssl-strict", FALSE, NULL);
2418 if (strlen(my_pair.what) == 8 && strncmp("cabundle", my_pair.what, 8) == 0) {
2419 g_object_set(G_OBJECT(client.net.session), SOUP_SESSION_SSL_CA_FILE, ca_bundle, NULL);
2421 if (strlen(my_pair.what) == 10 && strncmp("windowsize", my_pair.what, 10) == 0) {
2422 set_default_winsize(my_pair.value);
2424 if ((strlen(my_pair.what) == 2 && strncmp("hi", my_pair.what, 2) == 0) ||
2425 (strlen(my_pair.what) == 7 && strncmp("history", my_pair.what, 7) == 0)) {
2426 if (intval >= 0)
2427 set_command_history_len(intval);
2430 /* reload page? */
2431 if (browsersettings[i].reload)
2432 webkit_web_view_reload(client.gui.webview);
2433 return TRUE;
2436 return FALSE;
2439 gboolean
2440 process_line(char *line) {
2441 char *c = line, *command_hist;
2442 int i;
2443 size_t len, length = strlen(line);
2444 gboolean found = FALSE, success = FALSE;
2445 Arg a;
2446 GList *l;
2448 while (isspace(*c))
2449 c++;
2450 /* Ignore blank lines. */
2451 if (c[0] == '\0')
2452 return TRUE;
2454 command_hist = g_strdup(c);
2456 /* check for colon command aliases first */
2457 for (l = client.config.colon_aliases; l; l = g_list_next(l)) {
2458 Alias *alias = (Alias *)l->data;
2459 if (length == strlen(alias->alias) && strncmp(alias->alias, line, length) == 0) {
2460 /* reroute to target command */
2461 c = alias->target;
2462 length = strlen(alias->target);
2463 break;
2467 /* check standard commands */
2468 for (i = 0; i < LENGTH(commands); i++) {
2469 if (commands[i].cmd == NULL)
2470 break;
2471 len = strlen(commands[i].cmd);
2472 if (length >= len && !strncmp(c, commands[i].cmd, len) && (c[len] == ' ' || !c[len])) {
2473 found = TRUE;
2474 a.i = commands[i].arg.i;
2475 a.s = g_strdup(length > len + 1 ? &c[len + 1] : commands[i].arg.s);
2476 success = commands[i].func(&a);
2477 g_free(a.s);
2478 break;
2482 save_command_history(command_hist);
2483 g_free(command_hist);
2485 if (!found) {
2486 echo_message(Error, "Not a browser command: %s", c);
2487 } else if (!success) {
2488 if (client.state.error_msg != NULL) {
2489 echo_message(Error, client.state.error_msg);
2490 g_free(client.state.error_msg);
2491 client.state.error_msg = NULL;
2492 } else {
2493 echo_message(Error, "Unknown error. Please file a bug report!");
2496 return success;
2499 static gboolean
2500 search_tag(const Arg * a) {
2501 FILE *f;
2502 const char *filename;
2503 const char *tag = a->s;
2504 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
2505 int t, i, intag, k;
2507 if (!tag) {
2508 /* The user must give us something to load up. */
2509 set_error("Bookmark tag required with this option.");
2510 return FALSE;
2513 if (strlen(tag) > MAXTAGSIZE) {
2514 set_error("Tag too long.");
2515 return FALSE;
2518 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
2519 f = fopen(filename, "r");
2520 g_free((gpointer *)filename);
2521 if (f == NULL) {
2522 set_error("Couldn't open bookmarks file.");
2523 return FALSE;
2525 while (fgets(s, BUFFERSIZE-1, f)) {
2526 intag = 0;
2527 t = strlen(s) - 1;
2528 while (isspace(s[t]))
2529 t--;
2530 if (s[t] != ']') continue;
2531 while (t > 0) {
2532 if (s[t] == ']') {
2533 if (!intag)
2534 intag = t;
2535 else
2536 intag = 0;
2537 } else {
2538 if (s[t] == '[') {
2539 if (intag) {
2540 i = 0;
2541 k = t + 1;
2542 while (k < intag)
2543 foundtag[i++] = s[k++];
2544 foundtag[i] = '\0';
2545 /* foundtag now contains the tag */
2546 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
2547 i = 0;
2548 while (isspace(s[i])) i++;
2549 k = 0;
2550 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
2551 url[k] = '\0';
2552 Arg x = { .i = TargetNew, .s = url };
2553 open_arg(&x);
2556 intag = 0;
2559 t--;
2562 return TRUE;
2565 void
2566 toggle_proxy(gboolean onoff) {
2567 SoupURI *proxy_uri;
2568 char *filename, *new;
2570 if (onoff == FALSE) {
2571 g_object_set(client.net.session, "proxy-uri", NULL, NULL);
2572 } else {
2573 filename = (char *)g_getenv("http_proxy");
2575 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
2576 * defined.
2578 if (filename == NULL)
2579 filename = (char *)g_getenv("HTTP_PROXY");
2581 if (filename != NULL && 0 < strlen(filename)) {
2582 new = g_strrstr(filename, "http://") ? g_strdup(filename) : g_strdup_printf("http://%s", filename);
2583 proxy_uri = soup_uri_new(new);
2585 g_object_set(client.net.session, "proxy-uri", proxy_uri, NULL);
2587 soup_uri_free(proxy_uri);
2588 g_free(new);
2593 void
2594 toggle_scrollbars(gboolean onoff) {
2595 Gui *gui = &client.gui;
2596 if (onoff == TRUE) {
2597 gui->adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2598 gui->adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(gui->viewport));
2599 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2600 } else {
2601 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2602 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2603 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2605 gtk_widget_set_scroll_adjustments (GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2607 return;
2610 void set_default_winsize(const char * const size) {
2611 char *p;
2612 int x = 640, y = 480;
2614 x = strtol(size, &p, 10);
2615 if (errno == ERANGE || x <= 0) {
2616 x = 640;
2617 goto out;
2620 if (p == size || strlen(size) == p - size)
2621 goto out;
2623 y = strtol(p + 1, NULL, 10);
2624 if (errno == ERANGE || y <= 0)
2625 y = 480;
2627 out:
2628 gtk_window_resize(GTK_WINDOW(client.gui.window), x, y);
2631 void
2632 update_url(const char *uri) {
2633 Gui *gui = &client.gui;
2634 gboolean ssl = g_str_has_prefix(uri, "https://");
2635 GdkColor color;
2636 WebKitWebFrame *frame;
2637 WebKitWebDataSource *src;
2638 WebKitNetworkRequest *request;
2639 SoupMessage *msg;
2640 gboolean ssl_ok;
2641 char *sslactivecolor;
2642 gchar *markup;
2643 #ifdef ENABLE_HISTORY_INDICATOR
2644 char before[] = " [";
2645 char after[] = "]";
2646 gboolean back = webkit_web_view_can_go_back(gui->webview);
2647 gboolean fwd = webkit_web_view_can_go_forward(gui->webview);
2649 if (!back && !fwd)
2650 before[0] = after[0] = '\0';
2651 #endif
2652 markup = g_markup_printf_escaped(
2653 #ifdef ENABLE_HISTORY_INDICATOR
2654 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
2655 before, back ? "+" : "", fwd ? "-" : "", after
2656 #else
2657 "<span font=\"%s\">%s</span>", statusfont, uri
2658 #endif
2660 gtk_label_set_markup(GTK_LABEL(gui->status_url), markup);
2661 g_free(markup);
2662 if (ssl) {
2663 frame = webkit_web_view_get_main_frame(gui->webview);
2664 src = webkit_web_frame_get_data_source(frame);
2665 request = webkit_web_data_source_get_request(src);
2666 msg = webkit_network_request_get_message(request);
2667 ssl_ok = soup_message_get_flags(msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED;
2668 if (ssl_ok)
2669 sslactivecolor = sslbgcolor;
2670 else
2671 sslactivecolor = sslinvalidbgcolor;
2673 gdk_color_parse(ssl ? sslactivecolor : statusbgcolor, &color);
2674 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &color);
2675 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
2676 gtk_widget_modify_fg(GTK_WIDGET(gui->status_url), GTK_STATE_NORMAL, &color);
2677 gtk_widget_modify_fg(GTK_WIDGET(gui->status_state), GTK_STATE_NORMAL, &color);
2680 void
2681 update_state() {
2682 State* state = &client.state;
2683 char *markup;
2684 int download_count = g_list_length(state->activeDownloads);
2685 GString *status = g_string_new("");
2687 /* construct the status line */
2689 /* count, modkey and input buffer */
2690 g_string_append_printf(status, "%.0d", state->count);
2691 if (state->current_modkey) g_string_append_c(status, state->current_modkey);
2693 /* the number of active downloads */
2694 if (state->activeDownloads) {
2695 g_string_append_printf(status, " %d active %s", download_count,
2696 (download_count == 1) ? "download" : "downloads");
2699 #ifdef ENABLE_WGET_PROGRESS_BAR
2700 /* the progressbar */
2702 int progress = -1;
2703 char progressbar[progressbartick + 1];
2705 if (state->activeDownloads) {
2706 progress = 0;
2707 GList *ptr;
2709 for (ptr = state->activeDownloads; ptr; ptr = g_list_next(ptr)) {
2710 progress += 100 * webkit_download_get_progress(ptr->data);
2713 progress /= download_count;
2715 } else if (webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FINISHED
2716 && webkit_web_view_get_load_status(client.gui.webview) != WEBKIT_LOAD_FAILED) {
2718 progress = webkit_web_view_get_progress(client.gui.webview) * 100;
2721 if (progress >= 0) {
2722 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
2723 g_string_append_printf(status, " %c%s%c",
2724 progressborderleft, progressbar, progressborderright);
2727 #endif
2729 /* and the current scroll position */
2731 int max = gtk_adjustment_get_upper(client.gui.adjust_v) - gtk_adjustment_get_page_size(client.gui.adjust_v);
2732 int val = (int)(gtk_adjustment_get_value(client.gui.adjust_v) / max * 100);
2734 if (max == 0)
2735 g_string_append(status, " All");
2736 else if (val == 0)
2737 g_string_append(status, " Top");
2738 else if (val == 100)
2739 g_string_append(status, " Bot");
2740 else
2741 g_string_append_printf(status, " %d%%", val);
2745 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
2746 gtk_label_set_markup(GTK_LABEL(client.gui.status_state), markup);
2747 g_free(markup);
2749 g_string_free(status, TRUE);
2752 static void
2753 setup_client(void) {
2754 State *state = &client.state;
2755 Config *config = &client.config;
2757 state->mode = ModeNormal;
2758 state->count = 0;
2759 state->rememberedURI[0] = '\0';
2760 state->manual_focus = FALSE;
2761 state->is_inspecting = FALSE;
2762 state->commandhistory = NULL;
2763 state->commandpointer = 0;
2765 config->colon_aliases = NULL;
2766 config->cookie_timeout = 4800;
2769 void
2770 setup_modkeys() {
2771 unsigned int i;
2772 client.config.modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
2773 char *ptr = client.config.modkeys;
2775 for (i = 0; i < LENGTH(keys); i++)
2776 if (keys[i].modkey && !strchr(client.config.modkeys, keys[i].modkey))
2777 *(ptr++) = keys[i].modkey;
2778 client.config.modkeys = realloc(client.config.modkeys, &ptr[0] - &client.config.modkeys[0] + 1);
2781 void
2782 setup_gui() {
2783 Gui *gui = &client.gui;
2784 State *state = &client.state;
2786 gui->scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2787 gui->scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2788 gui->adjust_h = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_h));
2789 gui->adjust_v = gtk_range_get_adjustment(GTK_RANGE(gui->scroll_v));
2790 if (client.state.embed) {
2791 gui->window = GTK_WINDOW(gtk_plug_new(client.state.embed));
2792 } else {
2793 gui->window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2794 gtk_window_set_wmclass(GTK_WINDOW(gui->window), "vimprobable2", "Vimprobable2");
2796 gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
2797 gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2798 gui->inputbox = gtk_entry_new();
2799 gui->webview = (WebKitWebView*)webkit_web_view_new();
2800 gui->statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2801 gui->eventbox = gtk_event_box_new();
2802 gui->status_url = gtk_label_new(NULL);
2803 gui->status_state = gtk_label_new(NULL);
2804 GdkColor bg;
2805 PangoFontDescription *font;
2806 GdkGeometry hints = { 1, 1 };
2807 gui->inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(gui->webview));
2809 state->clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2810 state->clipboards[1] = gtk_clipboard_get(GDK_NONE);
2811 setup_settings();
2812 gdk_color_parse(statusbgcolor, &bg);
2813 gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
2814 gtk_widget_set_name(GTK_WIDGET(gui->window), "Vimprobable2");
2815 gtk_window_set_geometry_hints(gui->window, NULL, &hints, GDK_HINT_MIN_SIZE);
2817 state->keymap = gdk_keymap_get_default();
2819 #ifdef DISABLE_SCROLLBAR
2820 gui->viewport = gtk_scrolled_window_new(NULL, NULL);
2821 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gui->viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2822 #else
2823 /* Ensure we still see scrollbars. */
2824 gui->viewport = gtk_scrolled_window_new(gui->adjust_h, gui->adjust_v);
2825 #endif
2827 gui->pane = gtk_vpaned_new();
2828 gtk_paned_pack1(GTK_PANED(gui->pane), GTK_WIDGET(gui->box), TRUE, TRUE);
2830 setup_signals();
2831 gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
2833 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2834 * titlebars, we can still scroll.
2836 gtk_widget_set_scroll_adjustments(GTK_WIDGET(gui->webview), gui->adjust_h, gui->adjust_v);
2838 font = pango_font_description_from_string(urlboxfont[0]);
2839 gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
2840 pango_font_description_free(font);
2841 gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
2842 gtk_misc_set_alignment(GTK_MISC(gui->status_url), 0.0, 0.0);
2843 gtk_misc_set_alignment(GTK_MISC(gui->status_state), 1.0, 0.0);
2844 gtk_box_pack_start(gui->statusbar, gui->status_url, TRUE, TRUE, 2);
2845 gtk_box_pack_start(gui->statusbar, gui->status_state, FALSE, FALSE, 2);
2846 gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar));
2847 gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
2848 gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
2849 gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
2850 gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
2851 gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->pane));
2852 gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
2853 gtk_widget_show_all(GTK_WIDGET(gui->window));
2854 set_widget_font_and_color(gui->inputbox, urlboxfont[0], urlboxbgcolor[0], urlboxcolor[0]);
2855 g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
2858 void
2859 setup_settings() {
2860 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2861 char *filename, *file_url;
2863 client.net.session = webkit_get_default_session();
2864 g_object_set(G_OBJECT(client.net.session), "ssl-ca-file", ca_bundle, NULL);
2865 g_object_set(G_OBJECT(client.net.session), "ssl-strict", strict_ssl, NULL);
2866 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2867 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2868 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2869 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2870 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2871 g_object_set(G_OBJECT(settings), "enable-html5-local-storage", enableLocalstorage, NULL);
2872 g_object_set(G_OBJECT(settings), "enable-html5-database", enableDatabase, NULL);
2873 g_object_set(G_OBJECT(settings), "javascript-can-open-windows-automatically", javascriptPopups, NULL);
2874 filename = g_strdup_printf(USER_STYLESHEET);
2875 file_url = g_strdup_printf("file://%s", filename);
2876 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", file_url, NULL);
2877 g_free(file_url);
2878 g_free(filename);
2879 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2880 g_object_get(G_OBJECT(settings), "zoom-step", &client.config.zoomstep, NULL);
2881 webkit_web_view_set_settings(client.gui.webview, settings);
2883 /* proxy */
2884 toggle_proxy(use_proxy);
2887 void
2888 setup_signals() {
2889 WebKitWebFrame *frame = webkit_web_view_get_main_frame(client.gui.webview);
2890 #ifdef ENABLE_COOKIE_SUPPORT
2891 /* Headers. */
2892 g_signal_connect_after(G_OBJECT(client.net.session), "request-started", G_CALLBACK(new_generic_request), NULL);
2893 #endif
2894 /* Accept-language header */
2895 g_object_set(G_OBJECT(client.net.session), "accept-language", acceptlanguage, NULL);
2896 /* window */
2897 g_object_connect(G_OBJECT(client.gui.window),
2898 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2899 NULL);
2900 /* frame */
2901 g_signal_connect(G_OBJECT(frame),
2902 "scrollbars-policy-changed", G_CALLBACK(blank_cb), NULL);
2903 /* webview */
2904 g_object_connect(G_OBJECT(client.gui.webview),
2905 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2906 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2907 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2908 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2909 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2910 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2911 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2912 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2913 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2914 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2915 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2916 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2917 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2918 NULL);
2919 /* webview adjustment */
2920 g_object_connect(G_OBJECT(client.gui.adjust_v),
2921 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2922 NULL);
2923 /* inputbox */
2924 g_object_connect(G_OBJECT(client.gui.inputbox),
2925 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2926 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2927 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2928 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2929 NULL);
2930 /* inspector */
2931 g_signal_connect(G_OBJECT(client.gui.inspector),
2932 "inspect-web-view", G_CALLBACK(inspector_new_cb), NULL);
2933 g_signal_connect(G_OBJECT(client.gui.inspector),
2934 "show-window", G_CALLBACK(inspector_show_cb), NULL);
2935 g_signal_connect(G_OBJECT(client.gui.inspector),
2936 "close-window", G_CALLBACK(inspector_close_cb), NULL);
2937 g_signal_connect(G_OBJECT(client.gui.inspector),
2938 "finished", G_CALLBACK(inspector_finished_cb), NULL);
2941 #ifdef ENABLE_USER_SCRIPTFILE
2942 static void
2943 scripts_run_user_file() {
2944 gchar *js = NULL, *user_scriptfile = NULL;
2945 GError *error = NULL;
2947 user_scriptfile = g_strdup_printf(USER_SCRIPTFILE);
2949 /* run the users script file */
2950 if (g_file_test(user_scriptfile, G_FILE_TEST_IS_REGULAR)
2951 && g_file_get_contents(user_scriptfile, &js, NULL, &error)) {
2953 gchar *value = NULL, *message = NULL;
2955 jsapi_evaluate_script(js, &value, &message);
2956 g_free(js);
2957 if (message) {
2958 fprintf(stderr, "%s", message);
2960 g_free(value);
2961 g_free(message);
2962 } else {
2963 fprintf(stderr, "Cannot open %s: %s\n", user_scriptfile, error ? error->message : "file not found");
2966 g_free(user_scriptfile);
2968 #endif
2970 #ifdef ENABLE_COOKIE_SUPPORT
2971 void
2972 setup_cookies()
2974 Network *net = &client.net;
2975 if (net->file_cookie_jar)
2976 g_object_unref(net->file_cookie_jar);
2978 if (net->session_cookie_jar)
2979 g_object_unref(net->session_cookie_jar);
2981 net->session_cookie_jar = soup_cookie_jar_new();
2982 soup_cookie_jar_set_accept_policy(net->session_cookie_jar, CookiePolicy);
2984 net->cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2986 load_all_cookies();
2988 g_signal_connect(G_OBJECT(net->file_cookie_jar), "changed",
2989 G_CALLBACK(update_cookie_jar), NULL);
2992 /* This function could be used for any header requests we receive
2993 * for not, it's limited to handling cookies
2995 void
2996 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused)
2998 SoupMessageHeaders *soup_msg_h;
2999 SoupURI *uri;
3000 char *cookie_str;
3002 soup_msg_h = soup_msg->request_headers;
3003 soup_message_headers_remove(soup_msg_h, "Cookie");
3004 uri = soup_message_get_uri(soup_msg);
3005 soup_message_set_first_party(soup_msg, uri);
3006 if ((cookie_str = get_cookies(uri))) {
3007 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
3008 g_free(cookie_str);
3011 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_response_headers), NULL);
3013 return;
3016 char *
3017 get_cookies(SoupURI *soup_uri) {
3018 char *cookie_str;
3020 cookie_str = soup_cookie_jar_get_cookies(client.net.file_cookie_jar, soup_uri, TRUE);
3022 return cookie_str;
3025 void
3026 handle_response_headers(SoupMessage *soup_msg, gpointer unused)
3028 GSList *resp_cookie = NULL, *cookie_list;
3029 SoupCookie *cookie;
3030 SoupURI *uri = soup_message_get_uri(soup_msg);
3032 client.net.http_status = soup_msg->status_code;
3033 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_NEVER) {
3034 cookie_list = soup_cookies_from_response(soup_msg);
3035 for(resp_cookie = cookie_list; resp_cookie; resp_cookie = g_slist_next(resp_cookie))
3037 SoupDate *soup_date;
3038 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
3040 if (client.config.cookie_timeout && cookie->expires == NULL) {
3041 soup_date = soup_date_new_from_time_t(time(NULL) + client.config.cookie_timeout * 10);
3042 soup_cookie_set_expires(cookie, soup_date);
3043 soup_date_free(soup_date);
3045 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_ALWAYS) {
3046 /* no third party cookies: for libsoup 2.4 and later, the following should work */
3047 /*soup_cookie_jar_add_cookie_with_first_party(client.net.file_cookie_jar, uri, cookie);*/
3048 if (strcmp(soup_uri_get_host(uri), soup_cookie_get_domain(cookie)) == 0) {
3049 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
3051 } else {
3052 soup_cookie_jar_add_cookie(client.net.file_cookie_jar, cookie);
3056 soup_cookies_free(cookie_list);
3059 return;
3062 void
3063 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
3065 if (!new) {
3066 /* Nothing to do. */
3067 return;
3070 if (CookiePolicy != SOUP_COOKIE_JAR_ACCEPT_NEVER) {
3071 SoupCookie *copy;
3072 copy = soup_cookie_copy(new);
3074 soup_cookie_jar_add_cookie(client.net.session_cookie_jar, copy);
3077 return;
3080 void
3081 load_all_cookies(void)
3083 Network *net = &client.net;
3084 GSList *cookie_list;
3085 net->file_cookie_jar = soup_cookie_jar_text_new(net->cookie_store, COOKIES_STORAGE_READONLY);
3087 /* Put them back in the session store. */
3088 GSList *cookies_from_file = soup_cookie_jar_all_cookies(net->file_cookie_jar);
3089 cookie_list = cookies_from_file;
3091 for (; cookies_from_file;
3092 cookies_from_file = cookies_from_file->next)
3094 soup_cookie_jar_add_cookie(net->session_cookie_jar, cookies_from_file->data);
3097 soup_cookies_free(cookies_from_file);
3098 g_slist_free(cookie_list);
3100 return;
3103 #endif
3105 void
3106 mop_up(void) {
3107 /* Free up any nasty globals before exiting. */
3108 #ifdef ENABLE_COOKIE_SUPPORT
3109 if (client.net.cookie_store)
3110 g_free(client.net.cookie_store);
3111 #endif
3112 return;
3116 main(int argc, char *argv[]) {
3117 static Arg a;
3118 static char url[256] = "";
3119 static gboolean ver = false;
3120 static gboolean configfile_exists = FALSE;
3121 static const char *cfile = NULL;
3122 static gchar *winid = NULL;
3123 static GOptionEntry opts[] = {
3124 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
3125 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
3126 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
3127 { NULL }
3129 static GError *err;
3130 args = argv;
3131 Config *config = &client.config;
3133 /* command line argument: version */
3134 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
3135 g_printerr("can't init gtk: %s\n", err->message);
3136 g_error_free(err);
3137 return EXIT_FAILURE;
3140 if (ver) {
3141 printf("%s\n", INTERNAL_VERSION);
3142 return EXIT_SUCCESS;
3145 setup_client();
3147 if (getenv("TMPDIR")) {
3148 strncpy(temp_dir, getenv("TMPDIR"), MAX_SETTING_SIZE);
3149 temp_dir[MAX_SETTING_SIZE-1] = 0;
3152 if( getenv("XDG_CONFIG_HOME") )
3153 config->config_base = g_strdup_printf("%s", getenv("XDG_CONFIG_HOME"));
3154 else
3155 config->config_base = g_strdup_printf("%s/.config/", getenv("HOME"));
3157 sprintf(downloads_path, "%s", getenv("HOME"));
3159 if (cfile)
3160 config->configfile = g_strdup(cfile);
3161 else
3162 config->configfile = g_strdup_printf(RCFILE);
3164 #if !GLIB_CHECK_VERSION(2, 35, 0)
3165 if (!g_thread_supported()) {
3166 # if !GLIB_CHECK_VERSION(2, 32, 0)
3167 g_thread_init(NULL);
3168 # endif
3170 #endif
3172 if (winid) {
3173 if (strncmp(winid, "0x", 2) == 0) {
3174 client.state.embed = strtol(winid, NULL, 16);
3175 } else {
3176 client.state.embed = atoi(winid);
3180 setup_modkeys();
3181 make_keyslist();
3182 setup_gui();
3183 #ifdef ENABLE_COOKIE_SUPPORT
3184 setup_cookies();
3185 #endif
3187 make_searchengines_list(searchengines, LENGTH(searchengines));
3188 make_uri_handlers_list(uri_handlers, LENGTH(uri_handlers));
3190 /* Check if the specified file exists. */
3191 /* And only warn the user, if they explicitly asked for a config on the
3192 * command line.
3194 if (!(access(config->configfile, F_OK) == 0) && cfile) {
3195 echo_message(Info, "Config file '%s' doesn't exist", cfile);
3196 } else if ((access(config->configfile, F_OK) == 0))
3197 configfile_exists = true;
3199 /* read config file */
3200 /* But only report errors if we failed, and the file existed. */
3201 if ((SUCCESS != read_rcfile(config->configfile)) && configfile_exists) {
3202 echo_message(Error, "Error in config file '%s'", config->configfile);
3203 g_free(config->configfile);
3206 /* command line argument: URL */
3207 if (argc > 1) {
3208 strncpy(url, argv[argc - 1], 255);
3209 } else {
3210 strncpy(url, startpage, 255);
3213 a.i = TargetCurrent;
3214 a.s = url;
3215 open_arg(&a);
3216 gtk_main();
3218 mop_up();
3220 return EXIT_SUCCESS;