adding keypad slash, plus and minus to default keybindings
[vimprobable2.git] / main.c
blob307b98b684c0d05cfd1033579089f2fca070c1f7
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 Adam
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 /* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
19 #define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
20 #define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
22 /* remove unused bits, numlock symbol and buttons from keymask */
23 #define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
25 /* callbacks here */
26 static void inputbox_activate_cb(GtkEntry *entry, gpointer user_data);
27 static gboolean inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event);
28 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event);
29 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data);
30 static WebKitWebView* inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view);
31 static gboolean notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
32 static gboolean webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data);
33 static gboolean webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data);
34 static void webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data);
35 static gboolean webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event);
36 static void webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
37 static void webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
38 static gboolean webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
39 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data);
40 static gboolean webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
41 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data);
42 static gboolean webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data);
43 static void webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data);
44 static void webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data);
45 static void window_destroyed_cb(GtkWidget *window, gpointer func_data);
47 /* functions */
48 static gboolean bookmark(const Arg *arg);
49 static gboolean browser_settings(const Arg *arg);
50 static gboolean commandhistoryfetch(const Arg *arg);
51 static gboolean complete(const Arg *arg);
52 static gboolean descend(const Arg *arg);
53 gboolean echo(const Arg *arg);
54 static gboolean focus_input(const Arg *arg);
55 static gboolean input(const Arg *arg);
56 static gboolean navigate(const Arg *arg);
57 static gboolean number(const Arg *arg);
58 static gboolean open_arg(const Arg *arg);
59 static gboolean paste(const Arg *arg);
60 static gboolean quickmark(const Arg *arg);
61 static gboolean quit(const Arg *arg);
62 static gboolean revive(const Arg *arg);
63 static gboolean print_frame(const Arg *arg);
64 static gboolean search(const Arg *arg);
65 static gboolean set(const Arg *arg);
66 static gboolean script(const Arg *arg);
67 static gboolean scroll(const Arg *arg);
68 static gboolean search_tag(const Arg *arg);
69 static gboolean yank(const Arg *arg);
70 static gboolean view_source(const Arg * arg);
71 static gboolean zoom(const Arg *arg);
72 static gboolean fake_key_event(const Arg *arg);
74 static void update_url(const char *uri);
75 static void setup_modkeys(void);
76 static void setup_gui(void);
77 static void setup_settings(void);
78 static void setup_signals(void);
79 static void ascii_bar(int total, int state, char *string);
80 static gchar *jsapi_ref_to_string(JSContextRef context, JSValueRef ref);
81 static void jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message);
82 static void download_progress(WebKitDownload *d, GParamSpec *pspec);
83 static void set_widget_font_and_color(GtkWidget *widget, const char *font_str,
84 const char *bg_color_str, const char *fg_color_str);
86 static gboolean history(void);
87 static gboolean process_set_line(char *line);
88 void save_command_history(char *line);
89 void toggle_proxy(gboolean onoff);
90 void toggle_scrollbars(gboolean onoff);
92 gboolean process_keypress(GdkEventKey *event);
93 void fill_suggline(char * suggline, const char * command, const char *fill_with);
94 GtkWidget * fill_eventbox(const char * completion_line);
95 static void mop_up(void);
97 #include "main.h"
99 /* variables */
100 static GtkWindow *window;
101 static GtkWidget *viewport;
102 static GtkBox *box;
103 static GtkScrollbar *scroll_h;
104 static GtkScrollbar *scroll_v;
105 static GtkAdjustment *adjust_h;
106 static GtkAdjustment *adjust_v;
107 static GtkWidget *inputbox;
108 static GtkWidget *eventbox;
109 static GtkWidget *status_url;
110 static GtkWidget *status_state;
111 static WebKitWebView *webview;
112 static SoupSession *session;
113 static GtkClipboard *clipboards[2];
115 static char **args;
116 static unsigned int mode = ModeNormal;
117 static unsigned int count = 0;
118 static float zoomstep;
119 static char *modkeys;
120 static char current_modkey;
121 static char *search_handle;
122 static gboolean search_direction;
123 static gboolean echo_active = TRUE;
124 WebKitWebInspector *inspector;
126 static GdkNativeWindow embed = 0;
127 static char *configfile = NULL;
128 static char *winid = NULL;
130 static char rememberedURI[128] = "";
131 static char inputKey[5];
132 static char inputBuffer[65] = "";
133 static char chars[65] = "0000000000000000000000000000000000000000000000000000000000000000\n";
134 static char followTarget[8] = "";
135 char *error_msg = NULL;
137 GList *activeDownloads;
139 #include "config.h"
140 #include "keymap.h"
142 char commandhistory[COMMANDHISTSIZE][255];
143 int lastcommand = 0;
144 int maxcommands = 0;
145 int commandpointer = 0;
146 KeyList *keylistroot = NULL;
148 /* Cookie support. */
149 #ifdef ENABLE_COOKIE_SUPPORT
150 static SoupCookieJar *session_cookie_jar = NULL;
151 static SoupCookieJar *file_cookie_jar = NULL;
152 static time_t cookie_timeout = 4800;
153 static char *cookie_store;
154 static void setup_cookies(void);
155 static const char *get_cookies(SoupURI *soup_uri);
156 static void load_all_cookies(void);
157 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
158 static void update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new);
159 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
160 #endif
161 /* callbacks */
162 void
163 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
164 quit(NULL);
167 void
168 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
169 gtk_window_set_title(window, title);
172 void
173 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
174 #ifdef ENABLE_GTK_PROGRESS_BAR
175 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
176 #endif
177 update_state();
180 #ifdef ENABLE_WGET_PROGRESS_BAR
181 void
182 ascii_bar(int total, int state, char *string) {
183 int i;
185 for (i = 0; i < state; i++)
186 string[i] = progressbartickchar;
187 string[i++] = progressbarcurrent;
188 for (; i < total; i++)
189 string[i] = progressbarspacer;
190 string[i] = '\0';
192 #endif
194 void
195 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
196 Arg a = { .i = Silent, .s = JS_SETUP_HINTS };
197 const char *uri = webkit_web_view_get_uri(webview);
199 update_url(uri);
200 script(&a);
203 void
204 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
205 Arg a = { .i = Silent, .s = JS_SETUP_INPUT_FOCUS };
207 if (HISTORY_MAX_ENTRIES > 0)
208 history();
209 update_state();
210 script(&a);
213 static gboolean
214 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
215 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
216 if (strlen(rememberedURI) > 0) {
217 a.s = rememberedURI;
219 open_arg(&a);
220 return FALSE;
223 gboolean
224 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
225 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
226 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
227 open_arg(&a);
228 webkit_web_policy_decision_ignore(decision);
229 return TRUE;
232 gboolean
233 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
234 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
235 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
236 webkit_web_policy_decision_download(decision);
237 return TRUE;
238 } else {
239 return FALSE;
243 static WebKitWebView*
244 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
245 gchar* inspector_title;
246 GtkWidget* inspector_window;
247 GtkWidget* inspector_view;
249 /* just enough code to show the inspector - no signal handling etc. */
250 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
251 if (embed) {
252 inspector_window = gtk_plug_new(embed);
253 } else {
254 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
255 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
257 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
258 g_free(inspector_title);
259 inspector_view = webkit_web_view_new();
260 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
261 gtk_widget_show_all(inspector_window);
262 return WEBKIT_WEB_VIEW(inspector_view);
265 gboolean
266 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
267 const gchar *filename;
268 gchar *uri, *path;
269 uint32_t size;
270 Arg a;
272 filename = webkit_download_get_suggested_filename(download);
273 if (filename == NULL || strlen(filename) == 0) {
274 filename = "vimprobable_download";
276 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
277 uri = g_strconcat("file://", path, NULL);
278 webkit_download_set_destination_uri(download, uri);
279 g_free(uri);
280 size = (uint32_t)webkit_download_get_total_size(download);
281 a.i = Info;
282 if (size > 0)
283 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
284 else
285 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
286 echo(&a);
287 activeDownloads = g_list_prepend(activeDownloads, download);
288 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
289 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
290 update_state();
291 return TRUE;
294 void
295 download_progress(WebKitDownload *d, GParamSpec *pspec) {
296 Arg a;
297 WebKitDownloadStatus status = webkit_download_get_status(d);
299 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
300 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
301 a.i = Error;
302 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
303 echo(&a);
304 } else {
305 a.i = Info;
306 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
307 echo(&a);
309 activeDownloads = g_list_remove(activeDownloads, d);
311 update_state();
315 gboolean
316 process_keypress(GdkEventKey *event) {
317 KeyList *current;
319 current = keylistroot;
320 while (current != NULL) {
321 if (current->Element.mask == CLEAN(event->state)
322 && (current->Element.modkey == current_modkey
323 || (!current->Element.modkey && !current_modkey)
324 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
325 && current->Element.key == event->keyval
326 && current->Element.func)
327 if (current->Element.func(&current->Element.arg)) {
328 current_modkey = count = 0;
329 update_state();
330 return TRUE;
332 current = current->next;
334 return FALSE;
337 gboolean
338 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
339 Arg a = { .i = ModeNormal, .s = NULL };
341 switch (mode) {
342 case ModeNormal:
343 if (CLEAN(event->state) == 0) {
344 memset(inputBuffer, 0, 65);
345 if (event->keyval == GDK_Escape) {
346 a.i = Info;
347 a.s = g_strdup("");
348 echo(&a);
349 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
350 || (event->keyval == GDK_0 && count))) {
351 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
352 update_state();
353 return TRUE;
354 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
355 current_modkey = event->keyval;
356 update_state();
357 return TRUE;
360 /* keybindings */
361 if (process_keypress(event) == TRUE) return TRUE;
363 break;
364 case ModeInsert:
365 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
366 a.i = Silent;
367 a.s = "vimprobable_clearfocus()";
368 script(&a);
369 a.i = ModeNormal;
370 return set(&a);
372 case ModePassThrough:
373 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
374 echo(&a);
375 set(&a);
376 return TRUE;
378 break;
379 case ModeSendKey:
380 echo(&a);
381 set(&a);
382 break;
383 case ModeHints:
384 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
385 a.i = Silent;
386 a.s = "vimprobable_clear()";
387 script(&a);
388 a.i = ModeNormal;
389 count = 0;
390 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
391 return set(&a);
392 } else if (CLEAN(event->state) == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
393 || (event->keyval >= GDK_KP_1 && event->keyval <= GDK_KP_9)
394 || ((event->keyval == GDK_0 || event->keyval == GDK_KP_0) && count))) {
395 /* allow a zero as non-first number */
396 if (event->keyval >= GDK_KP_0 && event->keyval <= GDK_KP_9)
397 count = (count ? count * 10 : 0) + (event->keyval - GDK_KP_0);
398 else
399 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
400 memset(inputBuffer, 0, 65);
401 sprintf(inputBuffer, "%d", count);
402 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
403 a.i = Silent;
404 memset(inputBuffer, 0, 65);
405 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
406 script(&a);
407 update_state();
408 return TRUE;
409 } else if ((CLEAN(event->state) == 0 && (event->keyval >= GDK_a && event->keyval <= GDK_z))
410 || (CLEAN(event->state) == GDK_SHIFT_MASK && (event->keyval >= GDK_A && event->keyval <= GDK_Z))
411 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_space && event->keyval <= GDK_slash))
412 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_colon && event->keyval <= GDK_at))
413 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_braceleft && event->keyval <= GDK_umacron))
414 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Babovedot && event->keyval <= GDK_ycircumflex))
415 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_OE && event->keyval <= GDK_Ydiaeresis))
416 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_overline && event->keyval <= GDK_semivoicedsound))
417 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Farsi_0 && event->keyval <= GDK_Arabic_9))
418 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_semicolon && event->keyval <= GDK_Arabic_sukun))
419 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_madda_above && event->keyval <= GDK_Arabic_heh_goal))
420 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Cyrillic_GHE_bar && event->keyval <= GDK_Cyrillic_u_macron))
421 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Serbian_dje && event->keyval <= GDK_Korean_Won))
422 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Armenian_ligature_ew && event->keyval <= GDK_braille_dots_12345678))) {
423 /* update hints by link text */
424 if (strlen(inputBuffer) < 65) {
425 memset(inputKey, 0, 5);
426 /* support multibyte characters */
427 sprintf(inputKey, "%C", event->keyval);
428 strncat(inputBuffer, inputKey, 64 - strlen(inputBuffer));
429 /* remember the number of bytes of each character */
430 for (count = 0; count < 64; count++) {
431 if (strncmp((chars + count), "0", 1) == 0) {
432 sprintf(inputKey, "%d", (int)strlen(inputKey));
433 strncpy((chars + count), inputKey, 1);
434 break;
437 memset(inputKey, 0, 5);
438 count = 0;
439 a.i = Silent;
440 a.s = "vimprobable_cleanup()";
441 script(&a);
442 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
443 a.i = Silent;
444 script(&a);
445 update_state();
447 return TRUE;
448 } else if (CLEAN(event->state) == 0 && (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) && count) {
449 memset(inputBuffer, 0, 65);
450 sprintf(inputBuffer, "%d", count);
451 a.s = g_strconcat("vimprobable_fire(", inputBuffer, ")", NULL);
452 a.i = Silent;
453 script(&a);
454 memset(inputBuffer, 0, 65);
455 count = 0;
456 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
457 update_state();
458 return TRUE;
459 } else if (CLEAN(event->state) == 0 && event->keyval == GDK_BackSpace) {
460 if (count > 9) {
461 count /= 10;
462 memset(inputBuffer, 0, 65);
463 sprintf(inputBuffer, "%d", count);
464 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
465 a.i = Silent;
466 memset(inputBuffer, 0, 65);
467 script(&a);
468 update_state();
469 } else if (count > 0) {
470 count = 0;
471 memset(inputBuffer, 0, 65);
472 a.i = Silent;
473 a.s = "vimprobable_cleanup()";
474 script(&a);
475 a.s = g_strconcat("vimprobable_show_hints()", NULL);
476 a.i = Silent;
477 script(&a);
478 update_state();
479 } else if (strlen(inputBuffer) > 0) {
480 a.i = Silent;
481 a.s = "vimprobable_cleanup()";
482 script(&a);
483 /* check how many bytes the last character uses */
484 for (count = 0; count < 64; count++) {
485 if (strncmp((chars + count), "0", 1) == 0) {
486 break;
489 memset(inputKey, 0, 5);
490 strncpy(inputKey, (chars + count - 1), 1);
491 strncpy((chars + count - 1), "0", 1);
492 count = atoi(inputKey);
493 /* remove the appropriate number of bytes from the string */
494 strncpy((inputBuffer + strlen(inputBuffer) - count), "\0", 1);
495 count = 0;
496 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
497 a.i = Silent;
498 script(&a);
499 update_state();
501 return TRUE;
503 break;
505 return FALSE;
508 void
509 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
510 const char *fg_color_str) {
511 GdkColor fg_color;
512 GdkColor bg_color;
513 PangoFontDescription *font;
515 font = pango_font_description_from_string(font_str);
516 gtk_widget_modify_font(widget, font);
517 pango_font_description_free(font);
519 if (fg_color_str)
520 gdk_color_parse(fg_color_str, &fg_color);
521 if (bg_color_str)
522 gdk_color_parse(bg_color_str, &bg_color);
524 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
525 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
527 return;
530 void
531 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
532 const char *uri = webkit_web_view_get_uri(webview);
534 memset(rememberedURI, 0, 128);
535 if (link) {
536 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link));
537 strncpy(rememberedURI, link, 128);
538 } else
539 update_url(uri);
542 gboolean
543 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
544 Arg a;
546 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
547 if (gtk_window_has_toplevel_focus(window)) {
548 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
549 a.i = ModeNormal;
550 return set(&a);
551 } else if (!strcmp(message, "insertmode_on")) {
552 a.i = ModeInsert;
553 return set(&a);
556 return FALSE;
559 void
560 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
561 char *text;
562 guint16 length = gtk_entry_get_text_length(entry);
563 Arg a;
564 int i;
565 size_t len;
566 gboolean success = FALSE, forward = FALSE, found = FALSE;
568 a.i = HideCompletion;
569 complete(&a);
570 if (length < 2)
571 return;
572 text = (char*)gtk_entry_get_text(entry);
573 if (text[0] == ':') {
574 for (i = 0; i < LENGTH(commands); i++) {
575 if (commands[i].cmd == NULL)
576 break;
577 len = strlen(commands[i].cmd);
578 if (length >= len && !strncmp(&text[1], commands[i].cmd, len) && (text[len + 1] == ' ' || !text[len + 1])) {
579 found = TRUE;
580 a.i = commands[i].arg.i;
581 a.s = length > len + 2 ? &text[len + 2] : commands[i].arg.s;
582 success = commands[i].func(&a);
583 break;
587 save_command_history(text);
589 if (!found) {
590 a.i = Error;
591 a.s = g_strdup_printf("Not a browser command: %s", &text[1]);
592 echo(&a);
593 } else if (!success) {
594 a.i = Error;
595 if (error_msg != NULL) {
596 a.s = g_strdup_printf("%s", error_msg);
597 g_free(error_msg);
598 error_msg = NULL;
599 } else {
600 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
602 echo(&a);
604 } else if ((forward = text[0] == '/') || text[0] == '?') {
605 webkit_web_view_unmark_text_matches(webview);
606 #ifdef ENABLE_MATCH_HIGHLITING
607 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
608 webkit_web_view_set_highlight_text_matches(webview, TRUE);
609 #endif
610 count = 0;
611 #ifndef ENABLE_INCREMENTAL_SEARCH
612 a.s =& text[1];
613 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
614 search(&a);
615 #else
616 search_direction = forward;
617 search_handle = g_strdup(&text[1]);
618 #endif
619 } else
620 return;
621 if (!echo_active)
622 gtk_entry_set_text(entry, "");
623 gtk_widget_grab_focus(GTK_WIDGET(webview));
626 gboolean
627 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
628 Arg a;
630 switch (event->keyval) {
631 case GDK_Escape:
632 a.i = HideCompletion;
633 complete(&a);
634 a.i = ModeNormal;
635 return set(&a);
636 break;
637 case GDK_Tab:
638 a.i = DirectionNext;
639 return complete(&a);
640 break;
641 case GDK_Up:
642 a.i = DirectionPrev;
643 return commandhistoryfetch(&a);
644 break;
645 case GDK_Down:
646 a.i = DirectionNext;
647 return commandhistoryfetch(&a);
648 break;
649 case GDK_ISO_Left_Tab:
650 a.i = DirectionPrev;
651 return complete(&a);
652 break;
654 return FALSE;
657 gboolean
658 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
659 int i;
660 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
661 /* handle mouse click events */
662 for (i = 0; i < LENGTH(mouse); i++) {
663 if (mouse[i].mask == CLEAN(event->button.state)
664 && (mouse[i].modkey == current_modkey
665 || (!mouse[i].modkey && !current_modkey)
666 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
667 && mouse[i].button == event->button.button
668 && mouse[i].func) {
669 if (mouse[i].func(&mouse[i].arg)) {
670 current_modkey = count = 0;
671 update_state();
672 return TRUE;
677 return FALSE;
680 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
681 Arg a;
682 guint16 length = gtk_entry_get_text_length(entry);
684 if (!length) {
685 a.i = HideCompletion;
686 complete(&a);
687 a.i = ModeNormal;
688 return set(&a);
690 return FALSE;
693 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
694 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
695 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
696 gboolean forward = FALSE;
698 /* Update incremental search if the user changes the search text.
700 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
701 * from the user. But if the entry is focused and the text is set
702 * through gtk_entry_set_text() in some asyncrounous operation,
703 * I would consider that a bug.
706 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
707 webkit_web_view_unmark_text_matches(webview);
708 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
709 return TRUE;
712 return FALSE;
715 /* funcs here */
717 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
718 memset(suggline, 0, 512);
719 strncpy(suggline, command, 512);
720 strncat(suggline, " ", 1);
721 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
724 GtkWidget * fill_eventbox(const char * completion_line) {
725 GtkBox * row;
726 GtkWidget *row_eventbox, *el;
727 GdkColor color;
728 char * markup;
730 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
731 row_eventbox = gtk_event_box_new();
732 gdk_color_parse(completionbgcolor[0], &color);
733 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
734 el = gtk_label_new(NULL);
735 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
736 g_markup_escape_text(completion_line, strlen(completion_line)), "</span>", NULL);
737 gtk_label_set_markup(GTK_LABEL(el), markup);
738 g_free(markup);
739 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
740 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
741 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
742 return row_eventbox;
745 gboolean
746 complete(const Arg *arg) {
747 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
748 size_t listlen, len, cmdlen;
749 int i, spacepos;
750 Listelement *elementlist = NULL, *elementpointer;
751 gboolean highlight = FALSE;
752 GtkBox *row;
753 GtkWidget *row_eventbox, *el;
754 GtkBox *_table;
755 GdkColor color;
756 static GtkWidget *table, **widgets, *top_border;
757 static char **suggestions, *prefix;
758 static int n = 0, m, current = -1;
760 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
761 len = strlen(str);
762 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
763 return TRUE;
764 if (prefix) {
765 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
766 gdk_color_parse(completionbgcolor[0], &color);
767 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
768 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
769 if ((arg->i == DirectionNext && current == 0)
770 || (arg->i == DirectionPrev && current == n - 1))
771 current = -1;
772 } else {
773 free(widgets);
774 free(suggestions);
775 free(prefix);
776 gtk_widget_destroy(GTK_WIDGET(table));
777 gtk_widget_destroy(GTK_WIDGET(top_border));
778 table = NULL;
779 widgets = NULL;
780 suggestions = NULL;
781 prefix = NULL;
782 n = 0;
783 current = -1;
784 if (arg->i == HideCompletion)
785 return TRUE;
787 } else if (arg->i == HideCompletion)
788 return TRUE;
789 if (!widgets) {
790 prefix = g_strdup_printf(str);
791 widgets = malloc(sizeof(GtkWidget*) * MAX_LIST_SIZE);
792 suggestions = malloc(sizeof(char*) * MAX_LIST_SIZE);
793 top_border = gtk_event_box_new();
794 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
795 gdk_color_parse(completioncolor[2], &color);
796 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
797 table = gtk_event_box_new();
798 gdk_color_parse(completionbgcolor[0], &color);
799 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
800 highlight = len > 1;
801 if (strchr(str, ' ') == NULL) {
802 /* command completion */
803 listlen = LENGTH(commands);
804 for (i = 0; i < listlen; i++) {
805 if (commands[i].cmd == NULL)
806 break;
807 cmdlen = strlen(commands[i].cmd);
808 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
809 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
810 if (highlight) {
811 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
812 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
813 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
814 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
816 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
817 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
818 row_eventbox = gtk_event_box_new();
819 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
820 el = gtk_label_new(NULL);
821 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
822 free(s);
823 gtk_label_set_markup(GTK_LABEL(el), markup);
824 g_free(markup);
825 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
826 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
827 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
828 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
829 suggestions[n] = commands[i].cmd;
830 widgets[n++] = row_eventbox;
833 } else {
834 entry = (char *)malloc(512 * sizeof(char));
835 if (entry == NULL) {
836 return FALSE;
838 memset(entry, 0, 512);
839 suggurls = malloc(sizeof(char*) * MAX_LIST_SIZE);
840 if (suggurls == NULL) {
841 return FALSE;
843 spacepos = strcspn(str, " ");
844 searchfor = (str + spacepos + 1);
845 strncpy(command, (str + 1), spacepos - 1);
846 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
847 /* browser settings */
848 listlen = LENGTH(browsersettings);
849 for (i = 0; i < listlen; i++) {
850 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
851 /* match */
852 fill_suggline(suggline, command, browsersettings[i].name);
853 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
854 strncpy(suggurls[n], suggline, 512);
855 suggestions[n] = suggurls[n];
856 row_eventbox = fill_eventbox(suggline);
857 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
858 widgets[n++] = row_eventbox;
862 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
863 /* completion on tags */
864 spacepos = strcspn(str, " ");
865 searchfor = (str + spacepos + 1);
866 elementlist = complete_list(searchfor, 1, elementlist);
867 } else {
868 /* URL completion: bookmarks */
869 elementlist = complete_list(searchfor, 0, elementlist);
870 m = count_list(elementlist);
871 if (m < MAX_LIST_SIZE) {
872 /* URL completion: history */
873 elementlist = complete_list(searchfor, 2, elementlist);
876 elementpointer = elementlist;
877 while (elementpointer != NULL) {
878 fill_suggline(suggline, command, elementpointer->element);
879 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
880 strncpy(suggurls[n], suggline, 512);
881 suggestions[n] = suggurls[n];
882 row_eventbox = fill_eventbox(suggline);
883 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
884 widgets[n++] = row_eventbox;
885 elementpointer = elementpointer->next;
886 if (n >= MAX_LIST_SIZE)
887 break;
889 free_list(elementlist);
890 if (suggurls != NULL) {
891 free(suggurls);
892 suggurls = NULL;
894 if (entry != NULL) {
895 free(entry);
896 entry = NULL;
899 widgets = realloc(widgets, sizeof(GtkWidget*) * n);
900 suggestions = realloc(suggestions, sizeof(char*) * n);
901 if (!n) {
902 gdk_color_parse(completionbgcolor[1], &color);
903 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
904 el = gtk_label_new(NULL);
905 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
906 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
907 gtk_label_set_markup(GTK_LABEL(el), markup);
908 g_free(markup);
909 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
911 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
912 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
913 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
914 gtk_widget_show_all(GTK_WIDGET(window));
915 if (!n)
916 return TRUE;
917 current = arg->i == DirectionPrev ? n - 1 : 0;
919 if (current != -1) {
920 gdk_color_parse(completionbgcolor[2], &color);
921 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
922 s = g_strconcat(":", suggestions[current], NULL);
923 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
924 g_free(s);
925 } else
926 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
927 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
928 return TRUE;
931 gboolean
932 descend(const Arg *arg) {
933 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
934 int i, len;
935 count = count ? count : 1;
937 if (!source)
938 return TRUE;
939 if (arg->i == Rootdir) {
940 for (i = 0; i < 3; i++) /* get to the third slash */
941 if (!(p = strchr(++p, '/')))
942 return TRUE; /* if we cannot find it quit */
943 } else {
944 len = strlen(source);
945 if (!len) /* if string is empty quit */
946 return TRUE;
947 p = source + len; /* start at the end */
948 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
949 ++count;
950 for (i = 0; i < count; i++)
951 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
952 if (p == source) /* if we reach the first char pointer quit */
953 return TRUE;
954 ++p; /* since we do p-- in the while, we are pointing at
955 the char before the slash, so +1 */
957 len = p - source + 1; /* new length = end - start + 1 */
958 new = malloc(len + 1);
959 memcpy(new, source, len);
960 new[len] = '\0';
961 webkit_web_view_load_uri(webview, new);
962 free(new);
963 return TRUE;
966 gboolean
967 echo(const Arg *arg) {
968 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
970 if (index < Info || index > Error)
971 return TRUE;
973 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
974 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
976 /* TA: Always free arg->s here, rather than relying on the caller to do
977 * this.
979 if (arg->s)
980 g_free(arg->s);
982 return TRUE;
985 gboolean
986 input(const Arg *arg) {
987 int pos = 0;
988 count = 0;
989 const char *url;
990 int index = Info;
992 update_state();
994 /* Set the colour and font back to the default, so that we don't still
995 * maintain a red colour from a warning from an end of search indicator,
996 * etc.
998 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1000 /* to avoid things like :open URL :open URL2 or :open :open URL */
1001 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1002 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1003 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1004 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1005 gtk_widget_grab_focus(inputbox);
1006 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1007 return TRUE;
1010 gboolean
1011 navigate(const Arg *arg) {
1012 if (arg->i & NavigationForwardBack)
1013 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1014 else if (arg->i & NavigationReloadActions)
1015 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1016 else
1017 webkit_web_view_stop_loading(webview);
1018 return TRUE;
1021 gboolean
1022 number(const Arg *arg) {
1023 const char *source = webkit_web_view_get_uri(webview);
1024 char *uri, *p, *new;
1025 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1027 if (!source)
1028 return TRUE;
1029 uri = g_strdup_printf(source); /* copy string */
1030 p =& uri[0];
1031 while(*p != '\0') /* goto the end of the string */
1032 ++p;
1033 --p;
1034 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1035 --p;
1036 if (*(++p) == '\0') { /* if no numbers were found abort */
1037 free(uri);
1038 return TRUE;
1040 number = atoi(p) + diff; /* apply diff on number */
1041 *p = '\0';
1042 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1043 webkit_web_view_load_uri(webview, new);
1044 g_free(new);
1045 free(uri);
1046 return TRUE;
1049 gboolean
1050 open_arg(const Arg *arg) {
1051 char *argv[6];
1052 char *s = arg->s, *p, *new;
1053 Arg a = { .i = NavigationReload };
1054 int len, i;
1056 if (embed) {
1057 argv[0] = *args;
1058 argv[1] = "-e";
1059 argv[2] = winid;
1060 argv[3] = arg->s;
1061 argv[4] = NULL;
1062 } else {
1063 argv[0] = *args;
1064 argv[1] = arg->s;
1065 argv[2] = NULL;
1068 if (!arg->s)
1069 navigate(&a);
1070 else if (arg->i == TargetCurrent) {
1071 len = strlen(arg->s);
1072 new = NULL, p = strchr(arg->s, ' ');
1073 if (p) /* check for search engines */
1074 for (i = 0; i < LENGTH(searchengines); i++)
1075 if (!strncmp(arg->s, searchengines[i].handle, p - arg->s)) {
1076 p = soup_uri_encode(++p, "&");
1077 new = g_strdup_printf(searchengines[i].uri, p);
1078 g_free(p);
1079 break;
1081 if (!new) {
1082 if (len > 3 && strstr(arg->s, "://")) { /* valid url? */
1083 p = new = g_malloc(len + 1);
1084 while(*s != '\0') { /* strip whitespaces */
1085 if (*s != ' ')
1086 *(p++) = *s;
1087 ++s;
1089 *p = '\0';
1090 } else if (strcspn(arg->s, "/") == 0 || strcspn(arg->s, "./") == 0) { /* prepend "file://" */
1091 new = g_malloc(sizeof("file://") + len);
1092 strcpy(new, "file://");
1093 memcpy(&new[sizeof("file://") - 1], arg->s, len + 1);
1094 } else if (p || !strchr(arg->s, '.')) { /* whitespaces or no dot? */
1095 p = soup_uri_encode(arg->s, "&");
1096 new = g_strdup_printf(defsearch->uri, p);
1097 g_free(p);
1098 } else { /* prepend "http://" */
1099 new = g_malloc(sizeof("http://") + len);
1100 strcpy(new, "http://");
1101 memcpy(&new[sizeof("http://") - 1], arg->s, len + 1);
1104 webkit_web_view_load_uri(webview, new);
1105 g_free(new);
1106 } else
1107 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1108 return TRUE;
1111 gboolean
1112 yank(const Arg *arg) {
1113 const char *url, *feedback;
1115 if (arg->i & SourceURL) {
1116 url = webkit_web_view_get_uri(webview);
1117 if (!url)
1118 return TRUE;
1119 feedback = g_strconcat("Yanked ", url, NULL);
1120 give_feedback(feedback);
1121 if (arg->i & ClipboardPrimary)
1122 gtk_clipboard_set_text(clipboards[0], url, -1);
1123 if (arg->i & ClipboardGTK)
1124 gtk_clipboard_set_text(clipboards[1], url, -1);
1125 } else
1126 webkit_web_view_copy_clipboard(webview);
1127 return TRUE;
1130 gboolean
1131 paste(const Arg *arg) {
1132 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1134 /* If we're over a link, open it in a new target. */
1135 if (strlen(rememberedURI) > 0) {
1136 Arg new_target = { .i = TargetNew, .s = arg->s };
1137 open_arg(&new_target);
1138 return TRUE;
1141 if (arg->i & ClipboardPrimary)
1142 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1143 if (!a.s && arg->i & ClipboardGTK)
1144 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1145 if (a.s)
1146 open_arg(&a);
1147 return TRUE;
1150 gboolean
1151 quit(const Arg *arg) {
1152 FILE *f;
1153 const char *filename;
1154 const char *uri = webkit_web_view_get_uri(webview);
1155 if (uri != NULL) {
1156 /* write last URL into status file for recreation with "u" */
1157 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1158 f = fopen(filename, "w");
1159 if (f != NULL) {
1160 fprintf(f, "%s", uri);
1161 fclose(f);
1164 gtk_main_quit();
1165 return TRUE;
1168 gboolean
1169 revive(const Arg *arg) {
1170 FILE *f;
1171 const char *filename;
1172 char buffer[512] = "";
1173 Arg a = { .i = TargetNew, .s = NULL };
1174 /* get the URL of the window which has been closed last */
1175 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1176 f = fopen(filename, "r");
1177 if (f != NULL) {
1178 fgets(buffer, 512, f);
1179 fclose(f);
1181 if (strlen(buffer) > 0) {
1182 a.s = buffer;
1183 open_arg(&a);
1184 return TRUE;
1186 return FALSE;
1189 static
1190 gboolean print_frame(const Arg *arg)
1192 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1193 webkit_web_frame_print (frame);
1194 return TRUE;
1197 gboolean
1198 search(const Arg *arg) {
1199 count = count ? count : 1;
1200 gboolean success, direction = arg->i & DirectionPrev;
1201 Arg a;
1203 if (arg->s) {
1204 free(search_handle);
1205 search_handle = g_strdup_printf(arg->s);
1207 if (!search_handle)
1208 return TRUE;
1209 if (arg->i & DirectionAbsolute)
1210 search_direction = direction;
1211 else
1212 direction ^= search_direction;
1213 do {
1214 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1215 if (!success) {
1216 if (arg->i & Wrapping) {
1217 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1218 if (success) {
1219 a.i = Warning;
1220 a.s = g_strdup_printf("search hit %s, continuing at %s",
1221 direction ? "BOTTOM" : "TOP",
1222 direction ? "TOP" : "BOTTOM");
1223 echo(&a);
1224 } else
1225 break;
1226 } else
1227 break;
1229 } while(--count);
1230 if (!success) {
1231 a.i = Error;
1232 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1233 echo(&a);
1235 return TRUE;
1238 gboolean
1239 set(const Arg *arg) {
1240 Arg a = { .i = Info | NoAutoHide };
1242 switch (arg->i) {
1243 case ModeNormal:
1244 if (search_handle) {
1245 search_handle = NULL;
1246 webkit_web_view_unmark_text_matches(webview);
1248 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1249 gtk_widget_grab_focus(GTK_WIDGET(webview));
1250 break;
1251 case ModePassThrough:
1252 a.s = g_strdup("-- PASS THROUGH --");
1253 echo(&a);
1254 break;
1255 case ModeSendKey:
1256 a.s = g_strdup("-- PASS TROUGH (next) --");
1257 echo(&a);
1258 break;
1259 case ModeInsert: /* should not be called manually but automatically */
1260 a.s = g_strdup("-- INSERT --");
1261 echo(&a);
1262 break;
1263 case ModeHints:
1264 memset(followTarget, 0, 8);
1265 strncpy(followTarget, arg->s, 8);
1266 a.i = Silent;
1267 a.s = "vimprobable_show_hints()";
1268 script(&a);
1269 break;
1270 default:
1271 return TRUE;
1273 mode = arg->i;
1274 return TRUE;
1277 gchar*
1278 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1279 JSStringRef string_ref;
1280 gchar *string;
1281 size_t length;
1283 string_ref = JSValueToStringCopy(context, ref, NULL);
1284 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1285 string = g_new(gchar, length);
1286 JSStringGetUTF8CString(string_ref, string, length);
1287 JSStringRelease(string_ref);
1288 return string;
1291 void
1292 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1293 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1294 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1295 JSStringRef str;
1296 JSValueRef val, exception;
1298 str = JSStringCreateWithUTF8CString(script);
1299 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1300 JSStringRelease(str);
1301 if (!val)
1302 *message = jsapi_ref_to_string(context, exception);
1303 else
1304 *value = jsapi_ref_to_string(context, val);
1307 gboolean
1308 quickmark(const Arg *a) {
1309 int i, b;
1310 b = atoi(a->s);
1311 char *fn = g_strdup_printf(QUICKMARK_FILE);
1312 FILE *fp;
1313 fp = fopen(fn, "r");
1314 char buf[100];
1316 if (fp != NULL && b < 10) {
1317 for( i=0; i < b; ++i ) {
1318 if (feof(fp)) {
1319 break;
1321 fgets(buf, 100, fp);
1323 char *ptr = strrchr(buf, '\n');
1324 *ptr = '\0';
1325 Arg x = { .s = buf };
1326 if ( strlen(buf)) return open_arg(&x);
1327 else
1329 x.i = Error;
1330 x.s = g_strdup_printf("Quickmark %d not defined", b);
1331 echo(&x);
1332 return false;
1335 else { return false; }
1338 gboolean
1339 script(const Arg *arg) {
1340 gchar *value = NULL, *message = NULL;
1341 Arg a;
1343 if (!arg->s) {
1344 set_error("Missing argument.");
1345 return FALSE;
1347 jsapi_evaluate_script(arg->s, &value, &message);
1348 if (message) {
1349 set_error(message);
1350 return FALSE;
1352 if (arg->i != Silent && value) {
1353 a.i = arg->i;
1354 a.s = g_strdup(value);
1355 echo(&a);
1357 if (value) {
1358 if (strncmp(value, "fire;", 5) == 0) {
1359 count = 0;
1360 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1361 memset(inputBuffer, 0, 65);
1362 a.s = g_strconcat("vimprobable_fire(", (value + 5), ")", NULL);
1363 a.i = Silent;
1364 script(&a);
1365 } else if (strncmp(value, "open;", 5) == 0) {
1366 count = 0;
1367 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1368 memset(inputBuffer, 0, 65);
1369 a.i = ModeNormal;
1370 set(&a);
1371 if (strncmp(followTarget, "new", 3) == 0)
1372 a.i = TargetNew;
1373 else
1374 a.i = TargetCurrent;
1375 memset(followTarget, 0, 8);
1376 a.s = (value + 5);
1377 open_arg(&a);
1380 g_free(value);
1381 return TRUE;
1384 gboolean
1385 scroll(const Arg *arg) {
1386 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1387 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1388 float val = gtk_adjustment_get_value(adjust) / max * 100;
1389 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1391 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1392 if (arg->i & ScrollMove)
1393 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1394 direction * /* direction */
1395 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1396 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1397 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1398 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1399 else
1400 gtk_adjustment_set_value(adjust,
1401 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1402 update_state();
1404 return TRUE;
1407 gboolean
1408 zoom(const Arg *arg) {
1409 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1410 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1411 webkit_web_view_get_zoom_level(webview) +
1412 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1413 (count ? (float)count / 100.0 : 1.0));
1414 return TRUE;
1417 gboolean
1418 fake_key_event(const Arg *a) {
1419 if(!embed) {
1420 return FALSE;
1422 Arg err;
1423 err.i = Error;
1424 Display *xdpy;
1425 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1426 err.s = g_strdup("Couldn't find the XDisplay.");
1427 echo(&err);
1428 return FALSE;
1431 XKeyEvent xk;
1432 xk.display = xdpy;
1433 xk.subwindow = None;
1434 xk.time = CurrentTime;
1435 xk.same_screen = True;
1436 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1437 xk.window = embed;
1438 xk.state = a->i;
1440 if( ! a->s ) {
1441 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1442 echo(&err);
1443 return FALSE;
1446 KeySym keysym;
1447 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1448 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1449 echo(&err);
1450 return FALSE;
1453 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1454 err.s = g_strdup("Couldn't translate keysym to keycode");
1455 echo(&err);
1456 return FALSE;
1459 xk.type = KeyPress;
1460 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1461 err.s = g_strdup("XSendEvent failed");
1462 echo(&err);
1463 return FALSE;
1465 XFlush(xdpy);
1467 return TRUE;
1471 gboolean
1472 commandhistoryfetch(const Arg *arg) {
1473 if (arg->i == DirectionPrev) {
1474 commandpointer--;
1475 if (commandpointer < 0)
1476 commandpointer = maxcommands - 1;
1477 } else {
1478 commandpointer++;
1479 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1480 commandpointer = 0;
1483 if (commandpointer < 0)
1484 return FALSE;
1486 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1487 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1488 return TRUE;
1492 gboolean
1493 bookmark(const Arg *arg) {
1494 FILE *f;
1495 const char *filename;
1496 const char *uri = webkit_web_view_get_uri(webview);
1497 const char *title = webkit_web_view_get_title(webview);
1498 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1499 f = fopen(filename, "a");
1500 if (uri == NULL || strlen(uri) == 0) {
1501 set_error("No URI found to bookmark.");
1502 return FALSE;
1504 if (f != NULL) {
1505 fprintf(f, "%s", uri);
1506 if (title != NULL) {
1507 fprintf(f, "%s", " ");
1508 fprintf(f, "%s", title);
1510 if (arg->s && strlen(arg->s)) {
1511 build_taglist(arg, f);
1513 fprintf(f, "%s", "\n");
1514 fclose(f);
1515 give_feedback( "Bookmark saved" );
1516 return TRUE;
1517 } else {
1518 set_error("Bookmarks file not found.");
1519 return FALSE;
1523 gboolean
1524 history() {
1525 FILE *f;
1526 const char *filename;
1527 const char *uri = webkit_web_view_get_uri(webview);
1528 const char *title = webkit_web_view_get_title(webview);
1529 char *entry, buffer[512], *new;
1530 int n, i = 0;
1531 gboolean finished = FALSE;
1532 if (uri != NULL) {
1533 if (title != NULL) {
1534 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1535 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1536 } else {
1537 entry = malloc((strlen(uri) + 1) * sizeof(char));
1538 memset(entry, 0, strlen(uri) + 1);
1540 if (entry != NULL) {
1541 strncpy(entry, uri, strlen(uri));
1542 if (title != NULL) {
1543 strncat(entry, " ", 1);
1544 strncat(entry, title, strlen(title));
1546 n = strlen(entry);
1547 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1548 f = fopen(filename, "r");
1549 if (f != NULL) {
1550 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1551 if (new != NULL) {
1552 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1553 /* newest entries go on top */
1554 strncpy(new, entry, strlen(entry));
1555 strncat(new, "\n", 1);
1556 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1557 while (finished != TRUE) {
1558 if ((char *)NULL == fgets(buffer, 512, f)) {
1559 /* check if end of file was reached / error occured */
1560 if (!feof(f)) {
1561 break;
1563 /* end of file reached */
1564 finished = TRUE;
1565 continue;
1567 /* compare line (-1 because of newline character) */
1568 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1569 /* if the URI is already in history; we put it on top and skip it here */
1570 strncat(new, buffer, 512);
1571 i++;
1573 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1574 break;
1577 fclose(f);
1579 f = fopen(filename, "w");
1580 if (f != NULL) {
1581 fprintf(f, "%s", new);
1582 fclose(f);
1584 if (new != NULL) {
1585 free(new);
1586 new = NULL;
1590 if (entry != NULL) {
1591 free(entry);
1592 entry = NULL;
1595 return TRUE;
1598 static gboolean
1599 view_source(const Arg * arg) {
1600 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1601 webkit_web_view_set_view_source_mode(webview, !current_mode);
1602 webkit_web_view_reload(webview);
1603 return TRUE;
1606 static gboolean
1607 focus_input(const Arg *arg) {
1608 static Arg a;
1610 a.s = g_strconcat("vimprobable_focus_input()", NULL);
1611 a.i = Silent;
1612 script(&a);
1613 update_state();
1614 return TRUE;
1617 static gboolean
1618 browser_settings(const Arg *arg) {
1619 char line[255];
1620 if (!arg->s) {
1621 set_error("Missing argument.");
1622 return FALSE;
1624 strncpy(line, arg->s, 254);
1625 if (process_set_line(line))
1626 return TRUE;
1627 else {
1628 set_error("Invalid setting.");
1629 return FALSE;
1633 char *
1634 search_word(int whichword) {
1635 int k = 0;
1636 static char word[240];
1637 char *c = my_pair.line;
1639 while (isspace(*c) && *c)
1640 c++;
1642 switch (whichword) {
1643 case 0:
1644 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1645 word[k++] = *c;
1646 c++;
1648 word[k] = '\0';
1649 strncpy(my_pair.what, word, 20);
1650 break;
1651 case 1:
1652 while (*c && k < 240) {
1653 word[k++] = *c;
1654 c++;
1656 word[k] = '\0';
1657 strncpy(my_pair.value, word, 240);
1658 break;
1661 return c;
1664 static gboolean
1665 process_set_line(char *line) {
1666 char *c;
1667 int listlen, i;
1668 gboolean boolval;
1669 WebKitWebSettings *settings;
1671 settings = webkit_web_view_get_settings(webview);
1672 my_pair.line = line;
1673 c = search_word(0);
1674 if (!strlen(my_pair.what))
1675 return FALSE;
1677 while (isspace(*c) && *c)
1678 c++;
1680 if (*c == ':' || *c == '=')
1681 c++;
1683 my_pair.line = c;
1684 c = search_word(1);
1686 listlen = LENGTH(browsersettings);
1687 for (i = 0; i < listlen; i++) {
1688 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1689 /* mandatory argument not provided */
1690 if (strlen(my_pair.value) == 0)
1691 return FALSE;
1692 /* process acceptlanguage */
1693 if (strlen(my_pair.what) == 14 && strncmp("acceptlanguage", my_pair.what, 14) == 0) {
1694 strncpy(acceptlanguage, my_pair.value, strlen(my_pair.value) + 1);
1695 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
1696 return TRUE;
1698 /* process qmark? */
1699 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1700 return (process_save_qmark(my_pair.value, webview));
1702 /* interpret boolean values */
1703 if (browsersettings[i].boolval) {
1704 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) {
1705 boolval = TRUE;
1706 } 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) {
1707 boolval = FALSE;
1708 } else {
1709 return FALSE;
1711 } else if (browsersettings[i].colourval) {
1712 /* interpret as hexadecimal colour */
1713 if (!parse_colour(my_pair.value)) {
1714 return FALSE;
1717 if (browsersettings[i].var != NULL) {
1718 /* write value into internal variable */
1719 /*if (browsersettings[i].intval) {
1720 browsersettings[i].var = atoi(my_pair.value);
1721 } else {*/
1722 strncpy(browsersettings[i].var, my_pair.value, strlen(my_pair.value) + 1);
1723 /*}*/
1725 if (strlen(browsersettings[i].webkit) > 0) {
1726 /* activate appropriate webkit setting */
1727 if (browsersettings[i].boolval) {
1728 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1729 } else if (browsersettings[i].intval) {
1730 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1731 } else {
1732 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1734 webkit_web_view_set_settings(webview, settings);
1736 /* toggle proxy usage? */
1737 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1738 toggle_proxy(boolval);
1741 /* Toggle scrollbars. */
1742 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1743 toggle_scrollbars(boolval);
1745 /* case sensitivity of completion */
1746 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1747 complete_case_sensitive = boolval;
1749 /* reload page? */
1750 if (browsersettings[i].reload)
1751 webkit_web_view_reload(webview);
1752 return TRUE;
1755 return FALSE;
1758 gboolean
1759 process_line(char *line) {
1760 char *c = line;
1762 while (isspace(*c))
1763 c++;
1764 /* Ignore blank lines. */
1765 if (c[0] == '\0')
1766 return TRUE;
1767 if (strncmp(c, "map", 3) == 0) {
1768 c += 4;
1769 return process_map_line(c);
1770 } else if (strncmp(c, "set", 3) == 0) {
1771 c += 4;
1772 return process_set_line(c);
1774 return FALSE;
1777 static gboolean
1778 search_tag(const Arg * a) {
1779 FILE *f;
1780 const char *filename;
1781 const char *tag = a->s;
1782 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1783 int t, i, intag, k;
1785 if (!tag) {
1786 /* The user must give us something to load up. */
1787 set_error("Bookmark tag required with this option.");
1788 return FALSE;
1791 if (strlen(tag) > MAXTAGSIZE) {
1792 set_error("Tag too long.");
1793 return FALSE;
1796 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1797 f = fopen(filename, "r");
1798 if (f == NULL) {
1799 set_error("Couldn't open bookmarks file.");
1800 return FALSE;
1802 while (fgets(s, BUFFERSIZE-1, f)) {
1803 intag = 0;
1804 t = strlen(s) - 1;
1805 while (isspace(s[t]))
1806 t--;
1807 if (s[t] != ']') continue;
1808 while (t > 0) {
1809 if (s[t] == ']') {
1810 if (!intag)
1811 intag = t;
1812 else
1813 intag = 0;
1814 } else {
1815 if (s[t] == '[') {
1816 if (intag) {
1817 i = 0;
1818 k = t + 1;
1819 while (k < intag)
1820 foundtag[i++] = s[k++];
1821 foundtag[i] = '\0';
1822 /* foundtag now contains the tag */
1823 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
1824 i = 0;
1825 while (isspace(s[i])) i++;
1826 k = 0;
1827 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
1828 url[k] = '\0';
1829 Arg x = { .i = TargetNew, .s = url };
1830 open_arg(&x);
1833 intag = 0;
1836 t--;
1839 return TRUE;
1842 void
1843 toggle_proxy(gboolean onoff) {
1844 SoupURI *proxy_uri;
1845 char *filename, *new;
1846 int len;
1848 if (onoff == FALSE) {
1849 g_object_set(session, "proxy-uri", NULL, NULL);
1850 give_feedback("Proxy deactivated");
1851 } else {
1852 filename = (char *)g_getenv("http_proxy");
1854 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
1855 * defined.
1857 if (filename == NULL)
1858 filename = (char *)g_getenv("HTTP_PROXY");
1860 if (filename != NULL && 0 < (len = strlen(filename))) {
1861 if (strstr(filename, "://") == NULL) {
1862 /* prepend http:// */
1863 new = g_malloc(sizeof("http://") + len);
1864 strcpy(new, "http://");
1865 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
1866 proxy_uri = soup_uri_new(new);
1867 } else {
1868 proxy_uri = soup_uri_new(filename);
1870 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1871 give_feedback("Proxy activated");
1876 void
1877 toggle_scrollbars(gboolean onoff) {
1878 if (onoff == TRUE) {
1879 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
1880 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
1882 else {
1883 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1884 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1886 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
1888 return;
1891 void
1892 update_url(const char *uri) {
1893 gboolean ssl = g_str_has_prefix(uri, "https://");
1894 GdkColor color;
1895 #ifdef ENABLE_HISTORY_INDICATOR
1896 char before[] = " [";
1897 char after[] = "]";
1898 gboolean back = webkit_web_view_can_go_back(webview);
1899 gboolean fwd = webkit_web_view_can_go_forward(webview);
1901 if (!back && !fwd)
1902 before[0] = after[0] = '\0';
1903 #endif
1904 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped(
1905 #ifdef ENABLE_HISTORY_INDICATOR
1906 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
1907 before, back ? "+" : "", fwd ? "-" : "", after
1908 #else
1909 "<span font=\"%s\">%s</span>", statusfont, uri
1910 #endif
1912 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
1913 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
1914 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
1915 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
1916 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
1919 void
1920 update_state() {
1921 char *markup;
1922 int download_count = g_list_length(activeDownloads);
1923 GString *status = g_string_new("");
1925 /* construct the status line */
1927 /* count, modkey and input buffer */
1928 g_string_append_printf(status, "%.0d", count);
1929 if (current_modkey) g_string_append_c(status, current_modkey);
1930 if (inputBuffer[0]) g_string_append_printf(status, " %s", inputBuffer);
1932 /* the number of active downloads */
1933 if (activeDownloads) {
1934 g_string_append_printf(status, " %d active %s", download_count,
1935 (download_count == 1) ? "download" : "downloads");
1938 #ifdef ENABLE_WGET_PROGRESS_BAR
1939 /* the progressbar */
1941 int progress = -1;
1942 char progressbar[progressbartick + 1];
1944 if (activeDownloads) {
1945 progress = 0;
1946 GList *ptr;
1948 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
1949 progress += 100 * webkit_download_get_progress(ptr->data);
1952 progress /= download_count;
1954 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
1955 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
1957 progress = webkit_web_view_get_progress(webview) * 100;
1960 if (progress >= 0) {
1961 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
1962 g_string_append_printf(status, " %c%s%c",
1963 progressborderleft, progressbar, progressborderright);
1966 #endif
1968 /* and the current scroll position */
1970 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
1971 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
1973 if (max == 0)
1974 g_string_append(status, " All");
1975 else if (val == 0)
1976 g_string_append(status, " Top");
1977 else if (val == 100)
1978 g_string_append(status, " Bot");
1979 else
1980 g_string_append_printf(status, " %d%%", val);
1984 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
1985 gtk_label_set_markup(GTK_LABEL(status_state), markup);
1987 g_string_free(status, TRUE);
1990 void
1991 setup_modkeys() {
1992 unsigned int i;
1993 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
1994 char *ptr = modkeys;
1996 for (i = 0; i < LENGTH(keys); i++)
1997 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
1998 *(ptr++) = keys[i].modkey;
1999 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
2002 void
2003 setup_gui() {
2004 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2005 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2006 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2007 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2008 if (embed) {
2009 window = GTK_WINDOW(gtk_plug_new(embed));
2010 } else {
2011 window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
2012 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2014 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2015 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2016 inputbox = gtk_entry_new();
2017 webview = (WebKitWebView*)webkit_web_view_new();
2018 GtkBox *statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2019 eventbox = gtk_event_box_new();
2020 status_url = gtk_label_new(NULL);
2021 status_state = gtk_label_new(NULL);
2022 GdkColor bg;
2023 PangoFontDescription *font;
2024 GdkGeometry hints = { 1, 1 };
2025 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2027 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2028 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2029 setup_settings();
2030 gdk_color_parse(statusbgcolor, &bg);
2031 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2032 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2033 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2035 #ifdef DISABLE_SCROLLBAR
2036 viewport = gtk_scrolled_window_new(NULL, NULL);
2037 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2038 #else
2039 /* Ensure we still see scrollbars. */
2040 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2041 #endif
2043 setup_signals();
2044 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2046 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2047 * titlebars, we can still scroll.
2049 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2051 font = pango_font_description_from_string(urlboxfont[0]);
2052 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2053 pango_font_description_free(font);
2054 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2055 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2056 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2057 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2058 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2059 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2060 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2061 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2062 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2063 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2064 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2065 gtk_widget_grab_focus(GTK_WIDGET(webview));
2066 gtk_widget_show_all(GTK_WIDGET(window));
2069 void
2070 setup_settings() {
2071 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2072 SoupURI *proxy_uri;
2073 char *filename, *new;
2074 int len;
2076 session = webkit_get_default_session();
2077 g_object_set(G_OBJECT(settings), "default-font-size", DEFAULT_FONT_SIZE, NULL);
2078 g_object_set(G_OBJECT(settings), "enable-scripts", enablePlugins, NULL);
2079 g_object_set(G_OBJECT(settings), "enable-plugins", enablePlugins, NULL);
2080 g_object_set(G_OBJECT(settings), "enable-java-applet", enableJava, NULL);
2081 g_object_set(G_OBJECT(settings), "enable-page-cache", enablePagecache, NULL);
2082 filename = g_strdup_printf(USER_STYLESHEET);
2083 filename = g_strdup_printf("file://%s", filename);
2084 g_object_set(G_OBJECT(settings), "user-stylesheet-uri", filename, NULL);
2085 g_object_set(G_OBJECT(settings), "user-agent", useragent, NULL);
2086 g_object_get(G_OBJECT(settings), "zoom-step", &zoomstep, NULL);
2087 webkit_web_view_set_settings(webview, settings);
2089 /* proxy */
2090 if (use_proxy == TRUE) {
2091 filename = (char *)g_getenv("http_proxy");
2092 if (filename != NULL && 0 < (len = strlen(filename))) {
2093 if (strstr(filename, "://") == NULL) {
2094 /* prepend http:// */
2095 new = g_malloc(sizeof("http://") + len);
2096 strcpy(new, "http://");
2097 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2098 proxy_uri = soup_uri_new(new);
2099 } else {
2100 proxy_uri = soup_uri_new(filename);
2102 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2107 void
2108 setup_signals() {
2109 #ifdef ENABLE_COOKIE_SUPPORT
2110 /* Headers. */
2111 g_signal_connect_after(G_OBJECT(session), "request-started", G_CALLBACK(new_generic_request), NULL);
2112 #endif
2113 /* Accept-language header */
2114 g_object_set(G_OBJECT(session), "accept-language", acceptlanguage, NULL);
2115 /* window */
2116 g_object_connect(G_OBJECT(window),
2117 "signal::destroy", G_CALLBACK(window_destroyed_cb), NULL,
2118 NULL);
2119 /* webview */
2120 g_object_connect(G_OBJECT(webview),
2121 "signal::title-changed", G_CALLBACK(webview_title_changed_cb), NULL,
2122 "signal::load-progress-changed", G_CALLBACK(webview_progress_changed_cb), NULL,
2123 "signal::load-committed", G_CALLBACK(webview_load_committed_cb), NULL,
2124 "signal::load-finished", G_CALLBACK(webview_load_finished_cb), NULL,
2125 "signal::navigation-policy-decision-requested", G_CALLBACK(webview_navigation_cb), NULL,
2126 "signal::new-window-policy-decision-requested", G_CALLBACK(webview_new_window_cb), NULL,
2127 "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), NULL,
2128 "signal::download-requested", G_CALLBACK(webview_download_cb), NULL,
2129 "signal::key-press-event", G_CALLBACK(webview_keypress_cb), NULL,
2130 "signal::hovering-over-link", G_CALLBACK(webview_hoverlink_cb), NULL,
2131 "signal::console-message", G_CALLBACK(webview_console_cb), NULL,
2132 "signal::create-web-view", G_CALLBACK(webview_open_in_new_window_cb), NULL,
2133 "signal::event", G_CALLBACK(notify_event_cb), NULL,
2134 NULL);
2135 /* webview adjustment */
2136 g_object_connect(G_OBJECT(adjust_v),
2137 "signal::value-changed", G_CALLBACK(webview_scroll_cb), NULL,
2138 NULL);
2139 /* inputbox */
2140 g_object_connect(G_OBJECT(inputbox),
2141 "signal::activate", G_CALLBACK(inputbox_activate_cb), NULL,
2142 "signal::key-press-event", G_CALLBACK(inputbox_keypress_cb), NULL,
2143 "signal::key-release-event", G_CALLBACK(inputbox_keyrelease_cb), NULL,
2144 #ifdef ENABLE_INCREMENTAL_SEARCH
2145 "signal::changed", G_CALLBACK(inputbox_changed_cb), NULL,
2146 #endif
2147 NULL);
2148 /* inspector */
2149 g_signal_connect(G_OBJECT(inspector),
2150 "inspect-web-view", G_CALLBACK(inspector_inspect_web_view_cb), NULL);
2153 #ifdef ENABLE_COOKIE_SUPPORT
2154 void
2155 setup_cookies()
2157 if (file_cookie_jar)
2158 g_object_unref(file_cookie_jar);
2160 if (session_cookie_jar)
2161 g_object_unref(session_cookie_jar);
2163 session_cookie_jar = soup_cookie_jar_new();
2165 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2167 load_all_cookies();
2169 g_signal_connect(G_OBJECT(file_cookie_jar), "changed",
2170 G_CALLBACK(update_cookie_jar), NULL);
2172 return;
2175 /* TA: XXX - we should be using this callback for any header-requests we
2176 * receive (hence the name "new_generic_request" -- but for now, its use
2177 * is limited to handling cookies.
2179 void
2180 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2181 SoupMessageHeaders *soup_msg_h;
2182 SoupURI *uri;
2183 const char *cookie_str;
2185 soup_msg_h = soup_msg->request_headers;
2186 soup_message_headers_remove(soup_msg_h, "Cookie");
2187 uri = soup_message_get_uri(soup_msg);
2188 if( (cookie_str = get_cookies(uri)) )
2189 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2191 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2193 return;
2196 const char *
2197 get_cookies(SoupURI *soup_uri) {
2198 const char *cookie_str;
2200 cookie_str = soup_cookie_jar_get_cookies(file_cookie_jar, soup_uri, TRUE);
2202 return cookie_str;
2205 void
2206 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2208 GSList *resp_cookie = NULL;
2209 SoupCookie *cookie;
2211 for(resp_cookie = soup_cookies_from_response(soup_msg);
2212 resp_cookie;
2213 resp_cookie = g_slist_next(resp_cookie))
2215 SoupDate *soup_date;
2216 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2218 if (cookie_timeout && cookie->expires == NULL) {
2219 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2220 soup_cookie_set_expires(cookie, soup_date);
2222 soup_cookie_jar_add_cookie(file_cookie_jar, cookie);
2225 return;
2228 void
2229 update_cookie_jar(SoupCookieJar *jar, SoupCookie *old, SoupCookie *new)
2231 if (!new) {
2232 /* Nothing to do. */
2233 return;
2236 SoupCookie *copy;
2237 copy = soup_cookie_copy(new);
2239 soup_cookie_jar_add_cookie(session_cookie_jar, copy);
2241 return;
2244 void
2245 load_all_cookies(void)
2247 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2249 /* Put them back in the session store. */
2250 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2252 for (; cookies_from_file;
2253 cookies_from_file = cookies_from_file->next)
2255 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2258 soup_cookies_free(cookies_from_file);
2260 return;
2263 #endif
2265 void
2266 mop_up(void) {
2267 /* Free up any nasty globals before exiting. */
2268 #ifdef ENABLE_COOKIE_SUPPORT
2269 if (cookie_store)
2270 g_free(cookie_store);
2271 #endif
2272 return;
2276 main(int argc, char *argv[]) {
2277 static Arg a;
2278 static char url[256] = "";
2279 static gboolean ver = false;
2280 static gboolean configfile_exists = FALSE;
2281 static const char *cfile = NULL;
2282 static GOptionEntry opts[] = {
2283 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2284 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2285 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2286 { NULL }
2288 static GError *err;
2289 args = argv;
2291 /* command line argument: version */
2292 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2293 g_printerr("can't init gtk: %s\n", err->message);
2294 g_error_free(err);
2295 return EXIT_FAILURE;
2298 if (ver) {
2299 printf("%s\n", INTERNAL_VERSION);
2300 return EXIT_SUCCESS;
2303 if (cfile)
2304 configfile = g_strdup_printf(cfile);
2305 else
2306 configfile = g_strdup_printf(RCFILE);
2308 if (!g_thread_supported())
2309 g_thread_init(NULL);
2311 if (winid)
2312 embed = atoi(winid);
2314 setup_modkeys();
2315 make_keyslist();
2316 setup_gui();
2317 #ifdef ENABLE_COOKIE_SUPPORT
2318 setup_cookies();
2319 #endif
2321 /* Check if the specified file exists. */
2322 /* And only warn the user, if they explicitly asked for a config on the
2323 * command line.
2325 if (!(access(configfile, F_OK) == 0) && cfile) {
2326 char *feedback_str;
2328 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2329 give_feedback(feedback_str);
2330 } else if ((access(configfile, F_OK) == 0))
2331 configfile_exists = true;
2333 /* read config file */
2334 /* But only report errors if we failed, and the file existed. */
2335 if (!read_rcfile(configfile) && configfile_exists) {
2336 a.i = Error;
2337 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2338 echo(&a);
2339 g_free(configfile);
2342 /* command line argument: URL */
2343 if (argc > 1) {
2344 strncpy(url, argv[argc - 1], 255);
2345 } else {
2346 strncpy(url, startpage, 255);
2349 a.i = TargetCurrent;
2350 a.s = url;
2351 open_arg(&a);
2352 gtk_main();
2354 mop_up();
2356 return EXIT_SUCCESS;