version 0.9.6.0
[vimprobable2.git] / main.c
blob8276b9dc244ed0d1fee0204fb9c79172c69b0792
2 /*
3 (c) 2009 by Leon Winter
4 (c) 2009, 2010 by Hannes Schueller
5 (c) 2009, 2010 by Matto Fransen
6 (c) 2010 by Hans-Peter Deifel
7 (c) 2010 by Thomas Adams
8 see LICENSE file
9 */
11 #include <X11/Xlib.h>
12 #include "includes.h"
13 #include "vimprobable.h"
14 #include "utilities.h"
15 #include "callbacks.h"
16 #include "javascript.h"
18 /* remove numlock symbol from keymask */
19 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & ~(GDK_BUTTON1_MASK) & ~(GDK_BUTTON2_MASK) & ~(GDK_BUTTON3_MASK) & ~(GDK_BUTTON4_MASK) & ~(GDK_BUTTON5_MASK))
21 /* callbacks here */
22 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
23 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
24 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
25 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
26 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
27 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
28 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
29 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
30 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
31 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
32 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
33 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
34 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
35 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
36 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
37 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
38 static gboolean webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
39 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
40 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
41 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
43 /* functions */
44 static gboolean bookmark(const Arg *arg);
45 static gboolean browser_settings(const Arg *arg);
46 static gboolean commandhistoryfetch(const Arg *arg);
47 static gboolean complete(const Arg *arg);
48 static gboolean descend(const Arg *arg);
49 gboolean echo(const Arg *arg);
50 static gboolean focus_input(const Arg *arg);
51 static gboolean input(const Arg *arg);
52 static gboolean navigate(const Arg *arg);
53 static gboolean number(const Arg *arg);
54 static gboolean open_arg(const Arg *arg);
55 static gboolean paste(const Arg *arg);
56 static gboolean quickmark(const Arg *arg);
57 static gboolean quit(const Arg *arg);
58 static gboolean revive(const Arg *arg);
59 static gboolean print_frame(const Arg *arg);
60 static gboolean search(const Arg *arg);
61 static gboolean set(const Arg *arg);
62 static gboolean script(const Arg *arg);
63 static gboolean scroll(const Arg *arg);
64 static gboolean search_tag(const Arg *arg);
65 static gboolean yank(const Arg *arg);
66 static gboolean view_source(const Arg * arg);
67 static gboolean zoom(const Arg *arg);
68 static gboolean fake_key_event(const Arg *arg);
70 static void update_url(const char *uri);
71 static void setup_modkeys(void);
72 static void setup_gui(void);
73 static void setup_settings(void);
74 static void setup_signals(void);
75 static void ascii_bar(int total, int state, char *string);
76 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
77 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
78 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
79 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
80 const char *bg_color_str, const char *fg_color_str);
82 static gboolean history(void);
83 static gboolean process_set_line(char *line);
84 void save_command_history(char *line);
85 void toggle_proxy(gboolean onoff);
86 void toggle_scrollbars(gboolean onoff);
88 gboolean process_keypress(GdkEventKey *event);
89 void fill_suggline(char * suggline, const char * command, const char *fill_with);
90 GtkWidget * fill_eventbox(const char * completion_line);
91 static void mop_up(void);
93 #include "main.h"
95 /* variables */
96 static GtkWindow *window;
97 static GtkWidget *viewport;
98 static GtkBox *box;
99 static GtkScrollbar *scroll_h;
100 static GtkScrollbar *scroll_v;
101 static GtkAdjustment *adjust_h;
102 static GtkAdjustment *adjust_v;
103 static GtkWidget *inputbox;
104 static GtkWidget *eventbox;
105 static GtkWidget *status_url;
106 static GtkWidget *status_state;
107 static WebKitWebView *webview;
108 static SoupSession *session;
109 static GtkClipboard *clipboards[2];
111 static char **args;
112 static unsigned int mode = ModeNormal;
113 static unsigned int count = 0;
114 static float zoomstep;
115 static char *modkeys;
116 static char current_modkey;
117 static char *search_handle;
118 static gboolean search_direction;
119 static gboolean echo_active = TRUE;
120 WebKitWebInspector *inspector;
122 static GdkNativeWindow embed = 0;
123 static char *configfile = NULL;
124 static char *winid = NULL;
126 static char rememberedURI[128] = "";
127 static char inputKey[5];
128 static char inputBuffer[65] = "";
129 static char chars[65] = "0000000000000000000000000000000000000000000000000000000000000000\n";
130 static char followTarget[8] = "";
131 char *error_msg = NULL;
133 GList *activeDownloads;
135 #include "config.h"
136 #include "keymap.h"
138 char commandhistory[COMMANDHISTSIZE][255];
139 int lastcommand = 0;
140 int maxcommands = 0;
141 int commandpointer = 0;
142 KeyList *keylistroot = NULL;
144 /* Cookie support. */
145 #ifdef ENABLE_COOKIE_SUPPORT
146 static SoupCookieJar *session_cookie_jar = NULL;
147 static SoupCookieJar *file_cookie_jar = NULL;
148 static time_t cookie_timeout = 4800;
149 static char *cookie_store;
150 static void setup_cookies(void);
151 static const char *get_cookies(SoupURI *soup_uri);
152 static void load_all_cookies(void);
153 static void save_all_cookies(void);
154 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
155 static void update_cookie_jar(SoupCookie *new);
156 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
157 static int lock;
158 #endif
159 /* callbacks */
160 void
161 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
162 quit(NULL);
165 void
166 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
167 gtk_window_set_title(window, title);
170 void
171 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
172 #ifdef ENABLE_GTK_PROGRESS_BAR
173 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
174 #endif
175 update_state();
178 #ifdef ENABLE_WGET_PROGRESS_BAR
179 void
180 ascii_bar(int total, int state, char *string) {
181 int i;
183 for (i = 0; i < state; i++)
184 string[i] = progressbartickchar;
185 string[i++] = progressbarcurrent;
186 for (; i < total; i++)
187 string[i] = progressbarspacer;
188 string[i] = '\0';
190 #endif
192 void
193 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
194 Arg a = { .i = Silent, .s = JS_SETUP_HINTS };
195 const char *uri = webkit_web_view_get_uri(webview);
197 update_url(uri);
198 script(&a);
201 void
202 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
203 Arg a = { .i = Silent, .s = JS_SETUP_INPUT_FOCUS };
205 if (HISTORY_MAX_ENTRIES > 0)
206 history();
207 update_state();
208 script(&a);
211 static gboolean
212 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
213 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
214 if (strlen(rememberedURI) > 0) {
215 a.s = rememberedURI;
217 open_arg(&a);
218 return FALSE;
221 gboolean
222 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
223 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
224 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
225 open_arg(&a);
226 webkit_web_policy_decision_ignore(decision);
227 return TRUE;
230 gboolean
231 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
232 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
233 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
234 webkit_web_policy_decision_download(decision);
235 return TRUE;
236 } else {
237 return FALSE;
241 static WebKitWebView*
242 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
243 gchar* inspector_title;
244 GtkWidget* inspector_window;
245 GtkWidget* inspector_view;
247 /* just enough code to show the inspector - no signal handling etc. */
248 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
249 if (embed) {
250 inspector_window = gtk_plug_new(embed);
251 } else {
252 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
253 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
255 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
256 g_free(inspector_title);
257 inspector_view = webkit_web_view_new();
258 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
259 gtk_widget_show_all(inspector_window);
260 return WEBKIT_WEB_VIEW(inspector_view);
263 gboolean
264 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
265 const gchar *filename;
266 gchar *uri, *path;
267 uint32_t size;
268 Arg a;
270 filename = webkit_download_get_suggested_filename(download);
271 if (filename == NULL || strlen(filename) == 0) {
272 filename = "vimprobable_download";
274 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
275 uri = g_strconcat("file://", path, NULL);
276 webkit_download_set_destination_uri(download, uri);
277 g_free(uri);
278 size = (uint32_t)webkit_download_get_total_size(download);
279 a.i = Info;
280 if (size > 0)
281 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
282 else
283 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
284 echo(&a);
285 activeDownloads = g_list_prepend(activeDownloads, download);
286 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
287 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
288 update_state();
289 return TRUE;
292 void
293 download_progress(WebKitDownload *d, GParamSpec *pspec) {
294 Arg a;
295 WebKitDownloadStatus status = webkit_download_get_status(d);
297 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
298 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
299 a.i = Error;
300 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
301 echo(&a);
302 } else {
303 a.i = Info;
304 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
305 echo(&a);
307 activeDownloads = g_list_remove(activeDownloads, d);
309 update_state();
313 gboolean
314 process_keypress(GdkEventKey *event) {
315 KeyList *current;
317 current = keylistroot;
318 while (current != NULL) {
319 if (current->Element.mask == CLEAN(event->state)
320 && (current->Element.modkey == current_modkey
321 || (!current->Element.modkey && !current_modkey)
322 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
323 && current->Element.key == event->keyval
324 && current->Element.func)
325 if (current->Element.func(&current->Element.arg)) {
326 current_modkey = count = 0;
327 update_state();
328 return TRUE;
330 current = current->next;
332 return FALSE;
335 gboolean
336 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
337 Arg a = { .i = ModeNormal, .s = NULL };
339 switch (mode) {
340 case ModeNormal:
341 if (CLEAN(event->state) == 0) {
342 memset(inputBuffer, 0, 65);
343 if (event->keyval == GDK_Escape) {
344 a.i = Info;
345 a.s = g_strdup("");
346 echo(&a);
347 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
348 || (event->keyval == GDK_0 && count))) {
349 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
350 update_state();
351 return TRUE;
352 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
353 current_modkey = event->keyval;
354 update_state();
355 return TRUE;
358 /* keybindings */
359 if (process_keypress(event) == TRUE) return TRUE;
361 break;
362 case ModeInsert:
363 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
364 a.i = Silent;
365 a.s = "vimprobable_clearfocus()";
366 script(&a);
367 a.i = ModeNormal;
368 return set(&a);
370 case ModePassThrough:
371 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
372 echo(&a);
373 set(&a);
374 return TRUE;
376 break;
377 case ModeSendKey:
378 echo(&a);
379 set(&a);
380 break;
381 case ModeHints:
382 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
383 a.i = Silent;
384 a.s = "vimprobable_clear()";
385 script(&a);
386 a.i = ModeNormal;
387 count = 0;
388 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
389 return set(&a);
390 } else if (CLEAN(event->state) == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
391 || (event->keyval >= GDK_KP_1 && event->keyval <= GDK_KP_9)
392 || ((event->keyval == GDK_0 || event->keyval == GDK_KP_0) && count))) {
393 /* allow a zero as non-first number */
394 if (event->keyval >= GDK_KP_1 && event->keyval <= GDK_KP_9)
395 count = (count ? count * 10 : 0) + (event->keyval - GDK_KP_0);
396 else
397 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
398 memset(inputBuffer, 0, 65);
399 sprintf(inputBuffer, "%d", count);
400 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
401 a.i = Silent;
402 memset(inputBuffer, 0, 65);
403 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
404 script(&a);
405 update_state();
406 return TRUE;
407 } else if ((CLEAN(event->state) == 0 && (event->keyval >= GDK_a && event->keyval <= GDK_z))
408 || (CLEAN(event->state) == GDK_SHIFT_MASK && (event->keyval >= GDK_A && event->keyval <= GDK_Z))
409 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_space && event->keyval <= GDK_slash))
410 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_colon && event->keyval <= GDK_at))
411 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_braceleft && event->keyval <= GDK_umacron))
412 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Babovedot && event->keyval <= GDK_ycircumflex))
413 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_OE && event->keyval <= GDK_Ydiaeresis))
414 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_overline && event->keyval <= GDK_semivoicedsound))
415 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Farsi_0 && event->keyval <= GDK_Arabic_9))
416 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_semicolon && event->keyval <= GDK_Arabic_sukun))
417 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_madda_above && event->keyval <= GDK_Arabic_heh_goal))
418 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Cyrillic_GHE_bar && event->keyval <= GDK_Cyrillic_u_macron))
419 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Serbian_dje && event->keyval <= GDK_Korean_Won))
420 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Armenian_ligature_ew && event->keyval <= GDK_braille_dots_12345678))) {
421 /* update hints by link text */
422 if (strlen(inputBuffer) < 65) {
423 memset(inputKey, 0, 5);
424 /* support multibyte characters */
425 sprintf(inputKey, "%C", event->keyval);
426 strncat(inputBuffer, inputKey, 64 - strlen(inputBuffer));
427 /* remember the number of bytes of each character */
428 for (count = 0; count < 64; count++) {
429 if (strncmp((chars + count), "0", 1) == 0) {
430 sprintf(inputKey, "%d", (int)strlen(inputKey));
431 strncpy((chars + count), inputKey, 1);
432 break;
435 memset(inputKey, 0, 5);
436 count = 0;
437 a.i = Silent;
438 a.s = "vimprobable_cleanup()";
439 script(&a);
440 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
441 a.i = Silent;
442 script(&a);
443 update_state();
445 return TRUE;
446 } else if (CLEAN(event->state) == 0 && event->keyval == GDK_Return && count) {
447 memset(inputBuffer, 0, 65);
448 sprintf(inputBuffer, "%d", count);
449 a.s = g_strconcat("vimprobable_fire(", inputBuffer, ")", NULL);
450 a.i = Silent;
451 script(&a);
452 memset(inputBuffer, 0, 65);
453 count = 0;
454 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
455 update_state();
456 return TRUE;
457 } else if (CLEAN(event->state) == 0 && event->keyval == GDK_BackSpace) {
458 if (count > 9) {
459 count /= 10;
460 memset(inputBuffer, 0, 65);
461 sprintf(inputBuffer, "%d", count);
462 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
463 a.i = Silent;
464 memset(inputBuffer, 0, 65);
465 script(&a);
466 update_state();
467 } else if (count > 0) {
468 count = 0;
469 memset(inputBuffer, 0, 65);
470 a.i = Silent;
471 a.s = "vimprobable_cleanup()";
472 script(&a);
473 a.s = g_strconcat("vimprobable_show_hints()", NULL);
474 a.i = Silent;
475 script(&a);
476 update_state();
477 } else if (strlen(inputBuffer) > 0) {
478 a.i = Silent;
479 a.s = "vimprobable_cleanup()";
480 script(&a);
481 /* check how many bytes the last character uses */
482 for (count = 0; count < 64; count++) {
483 if (strncmp((chars + count), "0", 1) == 0) {
484 break;
487 memset(inputKey, 0, 5);
488 strncpy(inputKey, (chars + count - 1), 1);
489 strncpy((chars + count - 1), "0", 1);
490 count = atoi(inputKey);
491 /* remove the appropriate number of bytes from the string */
492 strncpy((inputBuffer + strlen(inputBuffer) - count), "\0", 1);
493 count = 0;
494 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
495 a.i = Silent;
496 script(&a);
497 update_state();
499 return TRUE;
501 break;
503 return FALSE;
506 void
507 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
508 const char *fg_color_str) {
509 GdkColor fg_color;
510 GdkColor bg_color;
511 PangoFontDescription *font;
513 font = pango_font_description_from_string(font_str);
514 gtk_widget_modify_font(widget, font);
515 pango_font_description_free(font);
517 if (fg_color_str)
518 gdk_color_parse(fg_color_str, &fg_color);
519 if (bg_color_str)
520 gdk_color_parse(bg_color_str, &bg_color);
522 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
523 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
525 return;
528 void
529 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
530 const char *uri = webkit_web_view_get_uri(webview);
532 memset(rememberedURI, 0, 128);
533 if (link) {
534 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link));
535 strncpy(rememberedURI, link, 128);
536 } else
537 update_url(uri);
540 gboolean
541 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
542 Arg a;
544 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
545 if (gtk_window_has_toplevel_focus(window)) {
546 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
547 a.i = ModeNormal;
548 return set(&a);
549 } else if (!strcmp(message, "insertmode_on")) {
550 a.i = ModeInsert;
551 return set(&a);
554 return FALSE;
557 void
558 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
559 char *text;
560 guint16 length = gtk_entry_get_text_length(entry);
561 Arg a;
562 int i;
563 size_t len;
564 gboolean success = FALSE, forward = FALSE, found = FALSE;
566 a.i = HideCompletion;
567 complete(&a);
568 if (length < 2)
569 return;
570 text = (char*)gtk_entry_get_text(entry);
571 if (text[0] == ':') {
572 for (i = 0; i < LENGTH(commands); i++) {
573 len = strlen(commands[i].cmd);
574 if (length >= len && !strncmp(&text[1], commands[i].cmd, len) && (text[len + 1] == ' ' || !text[len + 1])) {
575 found = TRUE;
576 a.i = commands[i].arg.i;
577 a.s = length > len + 2 ? &text[len + 2] : commands[i].arg.s;
578 success = commands[i].func(&a);
579 break;
583 save_command_history(text);
585 if (!found) {
586 a.i = Error;
587 a.s = g_strdup_printf("Not a browser command: %s", &text[1]);
588 echo(&a);
589 } else if (!success) {
590 a.i = Error;
591 if (error_msg != NULL) {
592 a.s = g_strdup_printf("%s", error_msg);
593 g_free(error_msg);
594 error_msg = NULL;
595 } else {
596 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
598 echo(&a);
600 } else if ((forward = text[0] == '/') || text[0] == '?') {
601 webkit_web_view_unmark_text_matches(webview);
602 #ifdef ENABLE_MATCH_HIGHLITING
603 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
604 webkit_web_view_set_highlight_text_matches(webview, TRUE);
605 #endif
606 count = 0;
607 #ifndef ENABLE_INCREMENTAL_SEARCH
608 a.s =& text[1];
609 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
610 search(&a);
611 #else
612 search_direction = forward;
613 search_handle = g_strdup(&text[1]);
614 #endif
615 } else
616 return;
617 if (!echo_active)
618 gtk_entry_set_text(entry, "");
619 gtk_widget_grab_focus(GTK_WIDGET(webview));
622 gboolean
623 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
624 Arg a;
626 switch (event->keyval) {
627 case GDK_Escape:
628 a.i = HideCompletion;
629 complete(&a);
630 a.i = ModeNormal;
631 return set(&a);
632 break;
633 case GDK_Tab:
634 a.i = DirectionNext;
635 return complete(&a);
636 break;
637 case GDK_Up:
638 a.i = DirectionPrev;
639 return commandhistoryfetch(&a);
640 break;
641 case GDK_Down:
642 a.i = DirectionNext;
643 return commandhistoryfetch(&a);
644 break;
645 case GDK_ISO_Left_Tab:
646 a.i = DirectionPrev;
647 return complete(&a);
648 break;
650 return FALSE;
653 gboolean
654 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
655 int i;
656 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
657 /* handle mouse click events */
658 for (i = 0; i < LENGTH(mouse); i++) {
659 if (mouse[i].mask == CLEAN(event->button.state)
660 && (mouse[i].modkey == current_modkey
661 || (!mouse[i].modkey && !current_modkey)
662 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
663 && mouse[i].button == event->button.button
664 && mouse[i].func) {
665 if (mouse[i].func(&mouse[i].arg)) {
666 current_modkey = count = 0;
667 update_state();
668 return TRUE;
673 return FALSE;
676 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
677 Arg a;
678 guint16 length = gtk_entry_get_text_length(entry);
680 if (!length) {
681 a.i = HideCompletion;
682 complete(&a);
683 a.i = ModeNormal;
684 return set(&a);
686 return FALSE;
689 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
690 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
691 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
692 gboolean forward = FALSE;
694 /* Update incremental search if the user changes the search text.
696 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
697 * from the user. But if the entry is focused and the text is set
698 * through gtk_entry_set_text() in some asyncrounous operation,
699 * I would consider that a bug.
702 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
703 webkit_web_view_unmark_text_matches(webview);
704 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
705 return TRUE;
708 return FALSE;
711 /* funcs here */
713 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
714 memset(suggline, 0, 512);
715 strncpy(suggline, command, 512);
716 strncat(suggline, " ", 1);
717 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
720 GtkWidget * fill_eventbox(const char * completion_line) {
721 GtkBox * row;
722 GtkWidget *row_eventbox, *el;
723 GdkColor color;
724 char * markup;
726 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
727 row_eventbox = gtk_event_box_new();
728 gdk_color_parse(completionbgcolor[0], &color);
729 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
730 el = gtk_label_new(NULL);
731 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
732 g_markup_escape_text(completion_line, strlen(completion_line)), "</span>", NULL);
733 gtk_label_set_markup(GTK_LABEL(el), markup);
734 g_free(markup);
735 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
736 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
737 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
738 return row_eventbox;
741 gboolean
742 complete(const Arg *arg) {
743 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
744 size_t listlen, len, cmdlen;
745 int i, spacepos;
746 Listelement *elementlist = NULL, *elementpointer;
747 gboolean highlight = FALSE;
748 GtkBox *row;
749 GtkWidget *row_eventbox, *el;
750 GtkBox *_table;
751 GdkColor color;
752 static GtkWidget *table, **widgets, *top_border;
753 static char **suggestions, *prefix;
754 static int n = 0, m, current = -1;
756 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
757 len = strlen(str);
758 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
759 return TRUE;
760 if (prefix) {
761 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
762 gdk_color_parse(completionbgcolor[0], &color);
763 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
764 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
765 if ((arg->i == DirectionNext && current == 0)
766 || (arg->i == DirectionPrev && current == n - 1))
767 current = -1;
768 } else {
769 free(widgets);
770 free(suggestions);
771 free(prefix);
772 gtk_widget_destroy(GTK_WIDGET(table));
773 gtk_widget_destroy(GTK_WIDGET(top_border));
774 table = NULL;
775 widgets = NULL;
776 suggestions = NULL;
777 prefix = NULL;
778 n = 0;
779 current = -1;
780 if (arg->i == HideCompletion)
781 return TRUE;
783 } else if (arg->i == HideCompletion)
784 return TRUE;
785 if (!widgets) {
786 prefix = g_strdup_printf(str);
787 widgets = malloc(sizeof(GtkWidget*) * MAX_LIST_SIZE);
788 suggestions = malloc(sizeof(char*) * MAX_LIST_SIZE);
789 top_border = gtk_event_box_new();
790 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
791 gdk_color_parse(completioncolor[2], &color);
792 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
793 table = gtk_event_box_new();
794 gdk_color_parse(completionbgcolor[0], &color);
795 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
796 highlight = len > 1;
797 if (strchr(str, ' ') == NULL) {
798 /* command completion */
799 listlen = LENGTH(commands);
800 for (i = 0; i < listlen; i++) {
801 cmdlen = strlen(commands[i].cmd);
802 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
803 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
804 if (highlight) {
805 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
806 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
807 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
808 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
810 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
811 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
812 row_eventbox = gtk_event_box_new();
813 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
814 el = gtk_label_new(NULL);
815 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
816 free(s);
817 gtk_label_set_markup(GTK_LABEL(el), markup);
818 g_free(markup);
819 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
820 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
821 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
822 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
823 suggestions[n] = commands[i].cmd;
824 widgets[n++] = row_eventbox;
827 } else {
828 entry = (char *)malloc(512 * sizeof(char));
829 if (entry == NULL) {
830 return FALSE;
832 memset(entry, 0, 512);
833 suggurls = malloc(sizeof(char*) * MAX_LIST_SIZE);
834 if (suggurls == NULL) {
835 return FALSE;
837 spacepos = strcspn(str, " ");
838 searchfor = (str + spacepos + 1);
839 strncpy(command, (str + 1), spacepos - 1);
840 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
841 /* browser settings */
842 listlen = LENGTH(browsersettings);
843 for (i = 0; i < listlen; i++) {
844 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
845 /* match */
846 fill_suggline(suggline, command, browsersettings[i].name);
847 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
848 strncpy(suggurls[n], suggline, 512);
849 suggestions[n] = suggurls[n];
850 row_eventbox = fill_eventbox(suggline);
851 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
852 widgets[n++] = row_eventbox;
856 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
857 /* completion on tags */
858 spacepos = strcspn(str, " ");
859 searchfor = (str + spacepos + 1);
860 elementlist = complete_list(searchfor, 1, elementlist);
861 } else {
862 /* URL completion: bookmarks */
863 elementlist = complete_list(searchfor, 0, elementlist);
864 m = count_list(elementlist);
865 if (m < MAX_LIST_SIZE) {
866 /* URL completion: history */
867 elementlist = complete_list(searchfor, 2, elementlist);
870 elementpointer = elementlist;
871 while (elementpointer != NULL) {
872 fill_suggline(suggline, command, elementpointer->element);
873 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
874 strncpy(suggurls[n], suggline, 512);
875 suggestions[n] = suggurls[n];
876 row_eventbox = fill_eventbox(suggline);
877 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
878 widgets[n++] = row_eventbox;
879 elementpointer = elementpointer->next;
880 if (n >= MAX_LIST_SIZE)
881 break;
883 free_list(elementlist);
884 if (suggurls != NULL) {
885 free(suggurls);
886 suggurls = NULL;
888 if (entry != NULL) {
889 free(entry);
890 entry = NULL;
893 widgets = realloc(widgets, sizeof(GtkWidget*) * n);
894 suggestions = realloc(suggestions, sizeof(char*) * n);
895 if (!n) {
896 gdk_color_parse(completionbgcolor[1], &color);
897 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
898 el = gtk_label_new(NULL);
899 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
900 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
901 gtk_label_set_markup(GTK_LABEL(el), markup);
902 g_free(markup);
903 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
905 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
906 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
907 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
908 gtk_widget_show_all(GTK_WIDGET(window));
909 if (!n)
910 return TRUE;
911 current = arg->i == DirectionPrev ? n - 1 : 0;
913 if (current != -1) {
914 gdk_color_parse(completionbgcolor[2], &color);
915 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
916 s = g_strconcat(":", suggestions[current], NULL);
917 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
918 g_free(s);
919 } else
920 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
921 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
922 return TRUE;
925 gboolean
926 descend(const Arg *arg) {
927 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
928 int i, len;
929 count = count ? count : 1;
931 if (!source)
932 return TRUE;
933 if (arg->i == Rootdir) {
934 for (i = 0; i < 3; i++) /* get to the third slash */
935 if (!(p = strchr(++p, '/')))
936 return TRUE; /* if we cannot find it quit */
937 } else {
938 len = strlen(source);
939 if (!len) /* if string is empty quit */
940 return TRUE;
941 p = source + len; /* start at the end */
942 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
943 ++count;
944 for (i = 0; i < count; i++)
945 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
946 if (p == source) /* if we reach the first char pointer quit */
947 return TRUE;
948 ++p; /* since we do p-- in the while, we are pointing at
949 the char before the slash, so +1 */
951 len = p - source + 1; /* new length = end - start + 1 */
952 new = malloc(len + 1);
953 memcpy(new, source, len);
954 new[len] = '\0';
955 webkit_web_view_load_uri(webview, new);
956 free(new);
957 return TRUE;
960 gboolean
961 echo(const Arg *arg) {
962 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
964 if (index < Info || index > Error)
965 return TRUE;
967 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
968 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
970 /* TA: Always free arg->s here, rather than relying on the caller to do
971 * this.
973 if (arg->s)
974 g_free(arg->s);
976 return TRUE;
979 gboolean
980 input(const Arg *arg) {
981 int pos = 0;
982 count = 0;
983 const char *url;
984 int index = Info;
986 update_state();
988 /* Set the colour and font back to the default, so that we don't still
989 * maintain a red colour from a warning from an end of search indicator,
990 * etc.
992 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
994 /* to avoid things like :open URL :open URL2 or :open :open URL */
995 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
996 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
997 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
998 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
999 gtk_widget_grab_focus(inputbox);
1000 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1001 return TRUE;
1004 gboolean
1005 navigate(const Arg *arg) {
1006 if (arg->i & NavigationForwardBack)
1007 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1008 else if (arg->i & NavigationReloadActions)
1009 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1010 else
1011 webkit_web_view_stop_loading(webview);
1012 return TRUE;
1015 gboolean
1016 number(const Arg *arg) {
1017 const char *source = webkit_web_view_get_uri(webview);
1018 char *uri, *p, *new;
1019 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1021 if (!source)
1022 return TRUE;
1023 uri = g_strdup_printf(source); /* copy string */
1024 p =& uri[0];
1025 while(*p != '\0') /* goto the end of the string */
1026 ++p;
1027 --p;
1028 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1029 --p;
1030 if (*(++p) == '\0') { /* if no numbers were found abort */
1031 free(uri);
1032 return TRUE;
1034 number = atoi(p) + diff; /* apply diff on number */
1035 *p = '\0';
1036 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1037 webkit_web_view_load_uri(webview, new);
1038 g_free(new);
1039 free(uri);
1040 return TRUE;
1043 gboolean
1044 open_arg(const Arg *arg) {
1045 char *argv[6];
1046 char *s = arg->s, *p, *new;
1047 Arg a = { .i = NavigationReload };
1048 int len, i;
1050 if (embed) {
1051 argv[0] = *args;
1052 argv[1] = "-e";
1053 argv[2] = winid;
1054 argv[3] = arg->s;
1055 argv[4] = NULL;
1056 } else {
1057 argv[0] = *args;
1058 argv[1] = arg->s;
1059 argv[2] = NULL;
1062 if (!arg->s)
1063 navigate(&a);
1064 else if (arg->i == TargetCurrent) {
1065 len = strlen(arg->s);
1066 new = NULL, p = strchr(arg->s, ' ');
1067 if (p) /* check for search engines */
1068 for (i = 0; i < LENGTH(searchengines); i++)
1069 if (!strncmp(arg->s, searchengines[i].handle, p - arg->s)) {
1070 p = soup_uri_encode(++p, "&");
1071 new = g_strdup_printf(searchengines[i].uri, p);
1072 g_free(p);
1073 break;
1075 if (!new) {
1076 if (len > 3 && strstr(arg->s, "://")) { /* valid url? */
1077 p = new = g_malloc(len + 1);
1078 while(*s != '\0') { /* strip whitespaces */
1079 if (*s != ' ')
1080 *(p++) = *s;
1081 ++s;
1083 *p = '\0';
1084 } else if (strcspn(arg->s, "/") == 0 || strcspn(arg->s, "./") == 0) { /* prepend "file://" */
1085 new = g_malloc(sizeof("file://") + len);
1086 strcpy(new, "file://");
1087 memcpy(&new[sizeof("file://") - 1], arg->s, len + 1);
1088 } else if (p || !strchr(arg->s, '.')) { /* whitespaces or no dot? */
1089 p = soup_uri_encode(arg->s, "&");
1090 new = g_strdup_printf(defsearch->uri, p);
1091 g_free(p);
1092 } else { /* prepend "http://" */
1093 new = g_malloc(sizeof("http://") + len);
1094 strcpy(new, "http://");
1095 memcpy(&new[sizeof("http://") - 1], arg->s, len + 1);
1098 webkit_web_view_load_uri(webview, new);
1099 g_free(new);
1100 } else
1101 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1102 return TRUE;
1105 gboolean
1106 yank(const Arg *arg) {
1107 const char *url, *feedback;
1109 if (arg->i & SourceURL) {
1110 url = webkit_web_view_get_uri(webview);
1111 if (!url)
1112 return TRUE;
1113 feedback = g_strconcat("Yanked ", url, NULL);
1114 give_feedback(feedback);
1115 if (arg->i & ClipboardPrimary)
1116 gtk_clipboard_set_text(clipboards[0], url, -1);
1117 if (arg->i & ClipboardGTK)
1118 gtk_clipboard_set_text(clipboards[1], url, -1);
1119 } else
1120 webkit_web_view_copy_clipboard(webview);
1121 return TRUE;
1124 gboolean
1125 paste(const Arg *arg) {
1126 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1128 /* If we're over a link, open it in a new target. */
1129 if (strlen(rememberedURI) > 0) {
1130 Arg new_target = { .i = TargetNew, .s = arg->s };
1131 open_arg(&new_target);
1132 return TRUE;
1135 if (arg->i & ClipboardPrimary)
1136 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1137 if (!a.s && arg->i & ClipboardGTK)
1138 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1139 if (a.s)
1140 open_arg(&a);
1141 return TRUE;
1144 gboolean
1145 quit(const Arg *arg) {
1146 FILE *f;
1147 const char *filename;
1148 const char *uri = webkit_web_view_get_uri(webview);
1149 if (uri != NULL) {
1150 /* write last URL into status file for recreation with "u" */
1151 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1152 f = fopen(filename, "w");
1153 if (f != NULL) {
1154 fprintf(f, "%s", uri);
1155 fclose(f);
1158 gtk_main_quit();
1159 return TRUE;
1162 gboolean
1163 revive(const Arg *arg) {
1164 FILE *f;
1165 const char *filename;
1166 char buffer[512] = "";
1167 Arg a = { .i = TargetNew, .s = NULL };
1168 /* get the URL of the window which has been closed last */
1169 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1170 f = fopen(filename, "r");
1171 if (f != NULL) {
1172 fgets(buffer, 512, f);
1173 fclose(f);
1175 if (strlen(buffer) > 0) {
1176 a.s = buffer;
1177 open_arg(&a);
1178 return TRUE;
1180 return FALSE;
1183 static
1184 gboolean print_frame(const Arg *arg)
1186 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1187 webkit_web_frame_print (frame);
1188 return TRUE;
1191 gboolean
1192 search(const Arg *arg) {
1193 count = count ? count : 1;
1194 gboolean success, direction = arg->i & DirectionPrev;
1195 Arg a;
1197 if (arg->s) {
1198 free(search_handle);
1199 search_handle = g_strdup_printf(arg->s);
1201 if (!search_handle)
1202 return TRUE;
1203 if (arg->i & DirectionAbsolute)
1204 search_direction = direction;
1205 else
1206 direction ^= search_direction;
1207 do {
1208 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1209 if (!success) {
1210 if (arg->i & Wrapping) {
1211 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1212 if (success) {
1213 a.i = Warning;
1214 a.s = g_strdup_printf("search hit %s, continuing at %s",
1215 direction ? "BOTTOM" : "TOP",
1216 direction ? "TOP" : "BOTTOM");
1217 echo(&a);
1218 } else
1219 break;
1220 } else
1221 break;
1223 } while(--count);
1224 if (!success) {
1225 a.i = Error;
1226 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1227 echo(&a);
1229 return TRUE;
1232 gboolean
1233 set(const Arg *arg) {
1234 Arg a = { .i = Info | NoAutoHide };
1236 switch (arg->i) {
1237 case ModeNormal:
1238 if (search_handle) {
1239 search_handle = NULL;
1240 webkit_web_view_unmark_text_matches(webview);
1242 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1243 gtk_widget_grab_focus(GTK_WIDGET(webview));
1244 break;
1245 case ModePassThrough:
1246 a.s = g_strdup("-- PASS THROUGH --");
1247 echo(&a);
1248 break;
1249 case ModeSendKey:
1250 a.s = g_strdup("-- PASS TROUGH (next) --");
1251 echo(&a);
1252 break;
1253 case ModeInsert: /* should not be called manually but automatically */
1254 a.s = g_strdup("-- INSERT --");
1255 echo(&a);
1256 break;
1257 case ModeHints:
1258 memset(followTarget, 0, 8);
1259 strncpy(followTarget, arg->s, 8);
1260 a.i = Silent;
1261 a.s = "vimprobable_show_hints()";
1262 script(&a);
1263 break;
1264 default:
1265 return TRUE;
1267 mode = arg->i;
1268 return TRUE;
1271 gchar*
1272 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1273 JSStringRef string_ref;
1274 gchar *string;
1275 size_t length;
1277 string_ref = JSValueToStringCopy(context, ref, NULL);
1278 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1279 string = g_new(gchar, length);
1280 JSStringGetUTF8CString(string_ref, string, length);
1281 JSStringRelease(string_ref);
1282 return string;
1285 void
1286 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1287 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1288 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1289 JSStringRef str;
1290 JSValueRef val, exception;
1292 str = JSStringCreateWithUTF8CString(script);
1293 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1294 JSStringRelease(str);
1295 if (!val)
1296 *message = jsapi_ref_to_string(context, exception);
1297 else
1298 *value = jsapi_ref_to_string(context, val);
1301 gboolean
1302 quickmark(const Arg *a) {
1303 int i, b;
1304 b = atoi(a->s);
1305 char *fn = g_strdup_printf(QUICKMARK_FILE);
1306 FILE *fp;
1307 fp = fopen(fn, "r");
1308 char buf[100];
1310 if (fp != NULL && b < 10) {
1311 for( i=0; i < b; ++i ) {
1312 if (feof(fp)) {
1313 break;
1315 fgets(buf, 100, fp);
1317 char *ptr = strrchr(buf, '\n');
1318 *ptr = '\0';
1319 Arg x = { .s = buf };
1320 if ( strlen(buf)) return open_arg(&x);
1321 else
1323 x.i = Error;
1324 x.s = g_strdup_printf("Quickmark %d not defined", b);
1325 echo(&x);
1326 return false;
1329 else { return false; }
1332 gboolean
1333 script(const Arg *arg) {
1334 gchar *value = NULL, *message = NULL;
1335 Arg a;
1337 if (!arg->s) {
1338 set_error("Missing argument.");
1339 return FALSE;
1341 jsapi_evaluate_script(arg->s, &value, &message);
1342 if (message) {
1343 set_error(message);
1344 return FALSE;
1346 if (arg->i != Silent && value) {
1347 a.i = arg->i;
1348 a.s = g_strdup(value);
1349 echo(&a);
1351 if (value) {
1352 if (strncmp(value, "fire;", 5) == 0) {
1353 count = 0;
1354 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1355 memset(inputBuffer, 0, 65);
1356 a.s = g_strconcat("vimprobable_fire(", (value + 5), ")", NULL);
1357 a.i = Silent;
1358 script(&a);
1359 } else if (strncmp(value, "open;", 5) == 0) {
1360 count = 0;
1361 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1362 memset(inputBuffer, 0, 65);
1363 a.i = ModeNormal;
1364 set(&a);
1365 if (strncmp(followTarget, "new", 3) == 0)
1366 a.i = TargetNew;
1367 else
1368 a.i = TargetCurrent;
1369 memset(followTarget, 0, 8);
1370 a.s = (value + 5);
1371 open_arg(&a);
1374 g_free(value);
1375 return TRUE;
1378 gboolean
1379 scroll(const Arg *arg) {
1380 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1381 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1382 float val = gtk_adjustment_get_value(adjust) / max * 100;
1383 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1385 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1386 if (arg->i & ScrollMove)
1387 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1388 direction * /* direction */
1389 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1390 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1391 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1392 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1393 else
1394 gtk_adjustment_set_value(adjust,
1395 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1396 update_state();
1398 return TRUE;
1401 gboolean
1402 zoom(const Arg *arg) {
1403 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1404 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1405 webkit_web_view_get_zoom_level(webview) +
1406 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1407 (count ? (float)count / 100.0 : 1.0));
1408 return TRUE;
1411 gboolean
1412 fake_key_event(const Arg *a) {
1413 if(!embed) {
1414 return FALSE;
1416 Arg err;
1417 err.i = Error;
1418 Display *xdpy;
1419 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1420 err.s = g_strdup("Couldn't find the XDisplay.");
1421 echo(&err);
1422 return FALSE;
1425 XKeyEvent xk;
1426 xk.display = xdpy;
1427 xk.subwindow = None;
1428 xk.time = CurrentTime;
1429 xk.same_screen = True;
1430 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1431 xk.window = embed;
1432 xk.state = a->i;
1434 if( ! a->s ) {
1435 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1436 echo(&err);
1437 return FALSE;
1440 KeySym keysym;
1441 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1442 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1443 echo(&err);
1444 return FALSE;
1447 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1448 err.s = g_strdup("Couldn't translate keysym to keycode");
1449 echo(&err);
1450 return FALSE;
1453 xk.type = KeyPress;
1454 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1455 err.s = g_strdup("XSendEvent failed");
1456 echo(&err);
1457 return FALSE;
1459 XFlush(xdpy);
1461 return TRUE;
1465 gboolean
1466 commandhistoryfetch(const Arg *arg) {
1467 if (arg->i == DirectionPrev) {
1468 commandpointer--;
1469 if (commandpointer < 0)
1470 commandpointer = maxcommands - 1;
1471 } else {
1472 commandpointer++;
1473 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1474 commandpointer = 0;
1477 if (commandpointer < 0)
1478 return FALSE;
1480 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1481 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1482 return TRUE;
1486 gboolean
1487 bookmark(const Arg *arg) {
1488 FILE *f;
1489 const char *filename;
1490 const char *uri = webkit_web_view_get_uri(webview);
1491 const char *title = webkit_web_view_get_title(webview);
1492 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1493 f = fopen(filename, "a");
1494 if (uri == NULL || strlen(uri) == 0) {
1495 set_error("No URI found to bookmark.");
1496 return FALSE;
1498 if (f != NULL) {
1499 fprintf(f, "%s", uri);
1500 if (title != NULL) {
1501 fprintf(f, "%s", " ");
1502 fprintf(f, "%s", title);
1504 if (arg->s && strlen(arg->s)) {
1505 build_taglist(arg, f);
1507 fprintf(f, "%s", "\n");
1508 fclose(f);
1509 give_feedback( "Bookmark saved" );
1510 return TRUE;
1511 } else {
1512 set_error("Bookmarks file not found.");
1513 return FALSE;
1517 gboolean
1518 history() {
1519 FILE *f;
1520 const char *filename;
1521 const char *uri = webkit_web_view_get_uri(webview);
1522 const char *title = webkit_web_view_get_title(webview);
1523 char *entry, buffer[512], *new;
1524 int n, i = 0;
1525 gboolean finished = FALSE;
1526 if (uri != NULL) {
1527 if (title != NULL) {
1528 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1529 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1530 } else {
1531 entry = malloc((strlen(uri) + 1) * sizeof(char));
1532 memset(entry, 0, strlen(uri) + 1);
1534 if (entry != NULL) {
1535 strncpy(entry, uri, strlen(uri));
1536 if (title != NULL) {
1537 strncat(entry, " ", 1);
1538 strncat(entry, title, strlen(title));
1540 n = strlen(entry);
1541 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1542 f = fopen(filename, "r");
1543 if (f != NULL) {
1544 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1545 if (new != NULL) {
1546 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1547 /* newest entries go on top */
1548 strncpy(new, entry, strlen(entry));
1549 strncat(new, "\n", 1);
1550 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1551 while (finished != TRUE) {
1552 if ((char *)NULL == fgets(buffer, 512, f)) {
1553 /* check if end of file was reached / error occured */
1554 if (!feof(f)) {
1555 break;
1557 /* end of file reached */
1558 finished = TRUE;
1559 continue;
1561 /* compare line (-1 because of newline character) */
1562 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1563 /* if the URI is already in history; we put it on top and skip it here */
1564 strncat(new, buffer, 512);
1565 i++;
1567 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1568 break;
1571 fclose(f);
1573 f = fopen(filename, "w");
1574 if (f != NULL) {
1575 fprintf(f, "%s", new);
1576 fclose(f);
1578 if (new != NULL) {
1579 free(new);
1580 new = NULL;
1584 if (entry != NULL) {
1585 free(entry);
1586 entry = NULL;
1589 return TRUE;
1592 static gboolean
1593 view_source(const Arg * arg) {
1594 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1595 webkit_web_view_set_view_source_mode(webview, !current_mode);
1596 webkit_web_view_reload(webview);
1597 return TRUE;
1600 static gboolean
1601 focus_input(const Arg *arg) {
1602 static Arg a;
1604 a.s = g_strconcat("vimprobable_focus_input()", NULL);
1605 a.i = Silent;
1606 script(&a);
1607 update_state();
1608 return TRUE;
1611 static gboolean
1612 browser_settings(const Arg *arg) {
1613 char line[255];
1614 if (!arg->s) {
1615 set_error("Missing argument.");
1616 return FALSE;
1618 strncpy(line, arg->s, 254);
1619 if (process_set_line(line))
1620 return TRUE;
1621 else {
1622 set_error("Invalid setting.");
1623 return FALSE;
1627 char *
1628 search_word(int whichword) {
1629 int k = 0;
1630 static char word[240];
1631 char *c = my_pair.line;
1633 while (isspace(*c) && *c)
1634 c++;
1636 switch (whichword) {
1637 case 0:
1638 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1639 word[k++] = *c;
1640 c++;
1642 word[k] = '\0';
1643 strncpy(my_pair.what, word, 20);
1644 break;
1645 case 1:
1646 while (*c && k < 240) {
1647 word[k++] = *c;
1648 c++;
1650 word[k] = '\0';
1651 strncpy(my_pair.value, word, 240);
1652 break;
1655 return c;
1658 static gboolean
1659 process_set_line(char *line) {
1660 char *c;
1661 int listlen, i;
1662 gboolean boolval;
1663 WebKitWebSettings *settings;
1665 settings = webkit_web_view_get_settings(webview);
1666 my_pair.line = line;
1667 c = search_word(0);
1668 if (!strlen(my_pair.what))
1669 return FALSE;
1671 while (isspace(*c) && *c)
1672 c++;
1674 if (*c == ':' || *c == '=')
1675 c++;
1677 my_pair.line = c;
1678 c = search_word(1);
1680 listlen = LENGTH(browsersettings);
1681 for (i = 0; i < listlen; i++) {
1682 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1683 /* mandatory argument not provided */
1684 if (strlen(my_pair.value) == 0)
1685 return FALSE;
1686 /* process qmark? */
1687 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1688 return (process_save_qmark(my_pair.value, webview));
1690 /* interpret boolean values */
1691 if (browsersettings[i].boolval) {
1692 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) {
1693 boolval = TRUE;
1694 } 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) {
1695 boolval = FALSE;
1696 } else {
1697 return FALSE;
1699 } else if (browsersettings[i].colourval) {
1700 /* interpret as hexadecimal colour */
1701 if (!parse_colour(my_pair.value)) {
1702 return FALSE;
1705 if (browsersettings[i].var != NULL) {
1706 /* write value into internal variable */
1707 /*if (browsersettings[i].intval) {
1708 browsersettings[i].var = atoi(my_pair.value);
1709 } else {*/
1710 strncpy(browsersettings[i].var, my_pair.value, strlen(my_pair.value) + 1);
1711 /*}*/
1713 if (strlen(browsersettings[i].webkit) > 0) {
1714 /* activate appropriate webkit setting */
1715 if (browsersettings[i].boolval) {
1716 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1717 } else if (browsersettings[i].intval) {
1718 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1719 } else {
1720 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1722 webkit_web_view_set_settings(webview, settings);
1724 /* toggle proxy usage? */
1725 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1726 toggle_proxy(boolval);
1729 /* Toggle scrollbars. */
1730 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1731 toggle_scrollbars(boolval);
1733 /* case sensitivity of completion */
1734 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1735 complete_case_sensitive = boolval;
1737 /* reload page? */
1738 if (browsersettings[i].reload)
1739 webkit_web_view_reload(webview);
1740 return TRUE;
1743 return FALSE;
1746 gboolean
1747 process_line(char *line) {
1748 char *c = line;
1750 while (isspace(*c))
1751 c++;
1752 /* Ignore blank lines. */
1753 if (c[0] == '\0')
1754 return TRUE;
1755 if (strncmp(c, "map", 3) == 0) {
1756 c += 4;
1757 return process_map_line(c);
1758 } else if (strncmp(c, "set", 3) == 0) {
1759 c += 4;
1760 return process_set_line(c);
1762 return FALSE;
1765 static gboolean
1766 search_tag(const Arg * a) {
1767 FILE *f;
1768 const char *filename;
1769 const char *tag = a->s;
1770 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1771 int t, i, intag, k;
1773 if (!tag) {
1774 /* The user must give us something to load up. */
1775 set_error("Bookmark tag required with this option.");
1776 return FALSE;
1779 if (strlen(tag) > MAXTAGSIZE) {
1780 set_error("Tag too long.");
1781 return FALSE;
1784 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1785 f = fopen(filename, "r");
1786 if (f == NULL) {
1787 set_error("Couldn't open bookmarks file.");
1788 return FALSE;
1790 while (fgets(s, BUFFERSIZE-1, f)) {
1791 intag = 0;
1792 t = strlen(s) - 1;
1793 while (isspace(s[t]))
1794 t--;
1795 if (s[t] != ']') continue;
1796 while (t > 0) {
1797 if (s[t] == ']') {
1798 if (!intag)
1799 intag = t;
1800 else
1801 intag = 0;
1802 } else {
1803 if (s[t] == '[') {
1804 if (intag) {
1805 i = 0;
1806 k = t + 1;
1807 while (k < intag)
1808 foundtag[i++] = s[k++];
1809 foundtag[i] = '\0';
1810 /* foundtag now contains the tag */
1811 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
1812 i = 0;
1813 while (isspace(s[i])) i++;
1814 k = 0;
1815 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
1816 url[k] = '\0';
1817 Arg x = { .i = TargetNew, .s = url };
1818 open_arg(&x);
1821 intag = 0;
1824 t--;
1827 return TRUE;
1830 void
1831 toggle_proxy(gboolean onoff) {
1832 SoupURI *proxy_uri;
1833 char *filename, *new;
1834 int len;
1836 if (onoff == FALSE) {
1837 g_object_set(session, "proxy-uri", NULL, NULL);
1838 give_feedback("Proxy deactivated");
1839 } else {
1840 filename = (char *)g_getenv("http_proxy");
1842 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
1843 * defined.
1845 if (filename == NULL)
1846 filename = (char *)g_getenv("HTTP_PROXY");
1848 if (filename != NULL && 0 < (len = strlen(filename))) {
1849 if (strstr(filename, "://") == NULL) {
1850 /* prepend http:// */
1851 new = g_malloc(sizeof("http://") + len);
1852 strcpy(new, "http://");
1853 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
1854 proxy_uri = soup_uri_new(new);
1855 } else {
1856 proxy_uri = soup_uri_new(filename);
1858 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1859 give_feedback("Proxy activated");
1864 void
1865 toggle_scrollbars(gboolean onoff) {
1866 if (onoff == TRUE) {
1867 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
1868 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
1870 else {
1871 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1872 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1874 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
1876 return;
1879 void
1880 update_url(const char *uri) {
1881 gboolean ssl = g_str_has_prefix(uri, "https://");
1882 GdkColor color;
1883 #ifdef ENABLE_HISTORY_INDICATOR
1884 char before[] = " [";
1885 char after[] = "]";
1886 gboolean back = webkit_web_view_can_go_back(webview);
1887 gboolean fwd = webkit_web_view_can_go_forward(webview);
1889 if (!back && !fwd)
1890 before[0] = after[0] = '\0';
1891 #endif
1892 gtk_label_set_markup((GtkLabel*)status_url, g_markup_printf_escaped(
1893 #ifdef ENABLE_HISTORY_INDICATOR
1894 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
1895 before, back ? "+" : "", fwd ? "-" : "", after
1896 #else
1897 "<span font=\"%s\">%s</span>", statusfont, uri
1898 #endif
1900 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
1901 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
1902 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
1903 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
1904 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
1907 void
1908 update_state() {
1909 char *markup;
1910 int download_count = g_list_length(activeDownloads);
1911 GString *status = g_string_new("");
1913 /* construct the status line */
1915 /* count, modkey and input buffer */
1916 g_string_append_printf(status, "%.0d", count);
1917 if (current_modkey) g_string_append_c(status, current_modkey);
1918 if (inputBuffer[0]) g_string_append_printf(status, " %s", inputBuffer);
1920 /* the number of active downloads */
1921 if (activeDownloads) {
1922 g_string_append_printf(status, " %d active %s", download_count,
1923 (download_count == 1) ? "download" : "downloads");
1926 #ifdef ENABLE_WGET_PROGRESS_BAR
1927 /* the progressbar */
1929 int progress = -1;
1930 char progressbar[progressbartick + 1];
1932 if (activeDownloads) {
1933 progress = 0;
1934 GList *ptr;
1936 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
1937 progress += 100 * webkit_download_get_progress(ptr->data);
1940 progress /= download_count;
1942 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
1943 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
1945 progress = webkit_web_view_get_progress(webview) * 100;
1948 if (progress >= 0) {
1949 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
1950 g_string_append_printf(status, " %c%s%c",
1951 progressborderleft, progressbar, progressborderright);
1954 #endif
1956 /* and the current scroll position */
1958 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
1959 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
1961 if (max == 0)
1962 g_string_append(status, " All");
1963 else if (val == 0)
1964 g_string_append(status, " Top");
1965 else if (val == 100)
1966 g_string_append(status, " Bot");
1967 else
1968 g_string_append_printf(status, " %d%%", val);
1972 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
1973 gtk_label_set_markup(GTK_LABEL(status_state), markup);
1975 g_string_free(status, TRUE);
1978 void
1979 setup_modkeys() {
1980 unsigned int i;
1981 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
1982 char *ptr = modkeys;
1984 for (i = 0; i < LENGTH(keys); i++)
1985 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
1986 *(ptr++) = keys[i].modkey;
1987 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
1990 void
1991 setup_gui() {
1992 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
1993 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
1994 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1995 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1996 if (embed) {
1997 window = (GtkWindow *)gtk_plug_new(embed);
1998 } else {
1999 window = (GtkWindow *)gtk_window_new(GTK_WINDOW_TOPLEVEL);
2000 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2002 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2003 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2004 inputbox = gtk_entry_new();
2005 webview = (WebKitWebView*)webkit_web_view_new();
2006 GtkBox *statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2007 eventbox = gtk_event_box_new();
2008 status_url = gtk_label_new(NULL);
2009 status_state = gtk_label_new(NULL);
2010 GdkColor bg;
2011 PangoFontDescription *font;
2012 GdkGeometry hints = { 1, 1 };
2013 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2015 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2016 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2017 setup_settings();
2018 gdk_color_parse(statusbgcolor, &bg);
2019 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2020 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2021 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2023 #ifdef DISABLE_SCROLLBAR
2024 viewport = gtk_scrolled_window_new(NULL, NULL);
2025 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2026 #else
2027 /* Ensure we still see scrollbars. */
2028 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2029 #endif
2031 setup_signals();
2032 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2034 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2035 * titlebars, we can still scroll.
2037 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2039 font = pango_font_description_from_string(urlboxfont[0]);
2040 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2041 pango_font_description_free(font);
2042 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2043 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2044 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2045 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2046 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2047 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2048 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2049 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2050 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2051 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2052 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2053 gtk_widget_grab_focus(GTK_WIDGET(webview));
2054 gtk_widget_show_all(GTK_WIDGET(window));
2057 void
2058 setup_settings() {
2059 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2060 SoupURI *proxy_uri;
2061 char *filename, *new;
2062 int len;
2064 session = webkit_get_default_session();
2065 g_object_set((GObject*)settings, "default-font-size", DEFAULT_FONT_SIZE, NULL);
2066 g_object_set((GObject*)settings, "enable-scripts", enablePlugins, NULL);
2067 g_object_set((GObject*)settings, "enable-plugins", enablePlugins, NULL);
2068 g_object_set((GObject*)settings, "enable-java-applet", enableJava, NULL);
2069 filename = g_strdup_printf(USER_STYLESHEET);
2070 filename = g_strdup_printf("file://%s", filename);
2071 g_object_set((GObject*)settings, "user-stylesheet-uri", filename, NULL);
2072 g_object_set((GObject*)settings, "user-agent", useragent, NULL);
2073 g_object_get((GObject*)settings, "zoom-step", &zoomstep, NULL);
2074 webkit_web_view_set_settings(webview, settings);
2076 /* proxy */
2077 if (use_proxy == TRUE) {
2078 filename = (char *)g_getenv("http_proxy");
2079 if (filename != NULL && 0 < (len = strlen(filename))) {
2080 if (strstr(filename, "://") == NULL) {
2081 /* prepend http:// */
2082 new = g_malloc(sizeof("http://") + len);
2083 strcpy(new, "http://");
2084 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2085 proxy_uri = soup_uri_new(new);
2086 } else {
2087 proxy_uri = soup_uri_new(filename);
2089 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2094 void
2095 setup_signals() {
2096 #ifdef ENABLE_COOKIE_SUPPORT
2097 /* Headers. */
2098 g_signal_connect_after((GObject*)session, "request-started", (GCallback)new_generic_request, NULL);
2099 #endif
2100 /* window */
2101 g_object_connect((GObject*)window,
2102 "signal::destroy", (GCallback)window_destroyed_cb, NULL,
2103 NULL);
2104 /* webview */
2105 g_object_connect((GObject*)webview,
2106 "signal::title-changed", (GCallback)webview_title_changed_cb, NULL,
2107 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, NULL,
2108 "signal::load-committed", (GCallback)webview_load_committed_cb, NULL,
2109 "signal::load-finished", (GCallback)webview_load_finished_cb, NULL,
2110 "signal::navigation-policy-decision-requested", (GCallback)webview_navigation_cb, NULL,
2111 "signal::new-window-policy-decision-requested", (GCallback)webview_new_window_cb, NULL,
2112 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, NULL,
2113 "signal::download-requested", (GCallback)webview_download_cb, NULL,
2114 "signal::key-press-event", (GCallback)webview_keypress_cb, NULL,
2115 "signal::hovering-over-link", (GCallback)webview_hoverlink_cb, NULL,
2116 "signal::console-message", (GCallback)webview_console_cb, NULL,
2117 "signal::create-web-view", (GCallback)webview_open_in_new_window_cb, NULL,
2118 "signal::event", (GCallback)notify_event_cb, NULL,
2119 NULL);
2120 /* webview adjustment */
2121 g_object_connect((GObject*)adjust_v,
2122 "signal::value-changed", (GCallback)webview_scroll_cb, NULL,
2123 NULL);
2124 /* inputbox */
2125 g_object_connect((GObject*)inputbox,
2126 "signal::activate", (GCallback)inputbox_activate_cb, NULL,
2127 "signal::key-press-event", (GCallback)inputbox_keypress_cb, NULL,
2128 "signal::key-release-event", (GCallback)inputbox_keyrelease_cb, NULL,
2129 #ifdef ENABLE_INCREMENTAL_SEARCH
2130 "signal::changed", (GCallback)inputbox_changed_cb, NULL,
2131 #endif
2132 NULL);
2133 /* inspector */
2134 g_signal_connect((GObject*)inspector,
2135 "inspect-web-view", (GCallback)inspector_inspect_web_view_cb, NULL);
2138 #ifdef ENABLE_COOKIE_SUPPORT
2139 void
2140 setup_cookies()
2142 if (file_cookie_jar)
2143 g_object_unref(file_cookie_jar);
2145 if (session_cookie_jar)
2146 g_object_unref(session_cookie_jar);
2148 session_cookie_jar = soup_cookie_jar_new();
2149 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2151 lock = open(cookie_store, 0);
2152 flock(lock, LOCK_EX);
2154 load_all_cookies();
2156 flock(lock, LOCK_UN);
2157 close(lock);
2159 soup_session_add_feature(session, SOUP_SESSION_FEATURE(session_cookie_jar));
2160 soup_session_add_feature(session, SOUP_SESSION_FEATURE(file_cookie_jar));
2162 return;
2165 /* TA: XXX - we should be using this callback for any header-requests we
2166 * receive (hence the name "new_generic_request" -- but for now, its use
2167 * is limited to handling cookies.
2169 void
2170 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2171 SoupMessageHeaders *soup_msg_h;
2172 SoupURI *uri;
2173 const char *cookie_str;
2175 soup_msg_h = soup_msg->request_headers;
2176 soup_message_headers_remove(soup_msg_h, "Cookie");
2177 uri = soup_message_get_uri(soup_msg);
2178 if( (cookie_str = get_cookies(uri)) )
2179 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2181 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2183 return;
2186 const char *
2187 get_cookies(SoupURI *soup_uri) {
2188 const char *cookie_str;
2190 cookie_str = soup_cookie_jar_get_cookies(session_cookie_jar, soup_uri, TRUE);
2192 return cookie_str;
2195 void
2196 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2198 GSList *resp_cookie = NULL;
2199 SoupCookie *cookie;
2201 for(resp_cookie = soup_cookies_from_response(soup_msg);
2202 resp_cookie;
2203 resp_cookie = g_slist_next(resp_cookie))
2205 SoupDate *soup_date;
2206 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2208 if (cookie_timeout && cookie->expires == NULL) {
2209 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2210 soup_cookie_set_expires(cookie, soup_date);
2212 soup_cookie_jar_add_cookie(session_cookie_jar, cookie);
2213 update_cookie_jar(cookie);
2216 return;
2219 void
2220 update_cookie_jar(SoupCookie *new)
2222 if (!new) {
2223 /* Nothing to do. */
2224 return;
2227 /* TA: Note that this locking is merely advisory -- because there's
2228 * no linking between different vimprobable processes, the cookie jar,
2229 * when being written to here, *WILL* be truncated and overwritten
2230 * with cookie contents from the specific session saving its cookies
2231 * at that time.
2233 * This may or may not contain cookies stored in other Vimprobable
2234 * instances, although when those instances save their cookies,
2235 * they'll just replace the cookie store's contents.
2237 * The locking should probably be changed to be fcntl() based one day
2238 * -- but the caveat with that is all Vimprobable instances would then
2239 * block waiting for the cookie file to become availabe. The
2240 * advisory locking used here so far seems to work OK, but if we run
2241 * into problems in the future, we'll know where to look.
2243 * Ideally, if Vimprobable were ever to want to do this cleanly,
2244 * something like using libunique to pass messages between registered
2245 * sessions.
2247 lock = open(cookie_store, 0);
2248 flock(lock, LOCK_EX);
2250 save_all_cookies();
2251 load_all_cookies();
2253 flock(lock, LOCK_UN);
2254 close(lock);
2256 return;
2259 void
2260 save_all_cookies()
2262 GSList *session_cookie_list = soup_cookie_jar_all_cookies(session_cookie_jar);
2264 for (; session_cookie_list;
2265 session_cookie_list = session_cookie_list->next)
2267 soup_cookie_jar_add_cookie(file_cookie_jar, session_cookie_list->data);
2270 soup_cookies_free(session_cookie_list);
2272 return;
2275 void
2276 load_all_cookies()
2278 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2280 /* Put them back in the session store. */
2281 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2283 for (; cookies_from_file;
2284 cookies_from_file = cookies_from_file->next)
2286 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2289 soup_cookies_free(cookies_from_file);
2291 return;
2294 #endif
2296 void
2297 mop_up(void) {
2298 /* Free up any nasty globals before exiting. */
2299 #ifdef ENABLE_COOKIE_SUPPORT
2300 if (cookie_store)
2301 g_free(cookie_store);
2302 #endif
2303 return;
2307 main(int argc, char *argv[]) {
2308 static Arg a;
2309 static char url[256] = "";
2310 static gboolean ver = false;
2311 static const char *cfile = NULL;
2312 static GOptionEntry opts[] = {
2313 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2314 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2315 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2316 { NULL }
2318 static GError *err;
2319 args = argv;
2321 /* command line argument: version */
2322 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2323 g_printerr("can't init gtk: %s\n", err->message);
2324 g_error_free(err);
2325 return EXIT_FAILURE;
2328 if (ver) {
2329 printf("%s\n", useragent);
2330 return EXIT_SUCCESS;
2333 if (cfile)
2334 configfile = g_strdup_printf(cfile);
2335 else
2336 configfile = g_strdup_printf(RCFILE);
2338 if (!g_thread_supported())
2339 g_thread_init(NULL);
2341 if (winid)
2342 embed = atoi(winid);
2344 setup_modkeys();
2345 make_keyslist();
2346 setup_gui();
2347 #ifdef ENABLE_COOKIE_SUPPORT
2348 setup_cookies();
2349 #endif
2351 /* read config file */
2352 if (!read_rcfile(configfile)) {
2353 free(configfile);
2354 a.i = Error;
2355 a.s = g_strdup_printf("Error in config file");
2356 echo(&a);
2359 /* command line argument: URL */
2360 if (argc > 1) {
2361 strncpy(url, argv[argc - 1], 255);
2362 } else {
2363 strncpy(url, startpage, 255);
2366 a.i = TargetCurrent;
2367 a.s = url;
2368 open_arg(&a);
2369 gtk_main();
2371 mop_up();
2373 return EXIT_SUCCESS;