Fixup function declarations.
[vimprobable2.git] / main.c
blob1869eb0ff594d6848b56fa101064d912996776f7
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 save_all_cookies(void);
158 static void new_generic_request(SoupSession *soup_ses, SoupMessage *soup_msg, gpointer unused);
159 static void update_cookie_jar(SoupCookie *new);
160 static void handle_cookie_request(SoupMessage *soup_msg, gpointer unused);
161 static int lock;
162 #endif
163 /* callbacks */
164 void
165 window_destroyed_cb(GtkWidget *window, gpointer func_data) {
166 quit(NULL);
169 void
170 webview_title_changed_cb(WebKitWebView *webview, WebKitWebFrame *frame, char *title, gpointer user_data) {
171 gtk_window_set_title(window, title);
174 void
175 webview_progress_changed_cb(WebKitWebView *webview, int progress, gpointer user_data) {
176 #ifdef ENABLE_GTK_PROGRESS_BAR
177 gtk_entry_set_progress_fraction(GTK_ENTRY(inputbox), progress == 100 ? 0 : (double)progress/100);
178 #endif
179 update_state();
182 #ifdef ENABLE_WGET_PROGRESS_BAR
183 void
184 ascii_bar(int total, int state, char *string) {
185 int i;
187 for (i = 0; i < state; i++)
188 string[i] = progressbartickchar;
189 string[i++] = progressbarcurrent;
190 for (; i < total; i++)
191 string[i] = progressbarspacer;
192 string[i] = '\0';
194 #endif
196 void
197 webview_load_committed_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
198 Arg a = { .i = Silent, .s = JS_SETUP_HINTS };
199 const char *uri = webkit_web_view_get_uri(webview);
201 update_url(uri);
202 script(&a);
205 void
206 webview_load_finished_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
207 Arg a = { .i = Silent, .s = JS_SETUP_INPUT_FOCUS };
209 if (HISTORY_MAX_ENTRIES > 0)
210 history();
211 update_state();
212 script(&a);
215 static gboolean
216 webview_open_in_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, gpointer user_data) {
217 Arg a = { .i = TargetNew, .s = (char*)webkit_web_view_get_uri(webview) };
218 if (strlen(rememberedURI) > 0) {
219 a.s = rememberedURI;
221 open_arg(&a);
222 return FALSE;
225 gboolean
226 webview_new_window_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
227 WebKitWebNavigationAction *action, WebKitWebPolicyDecision *decision, gpointer user_data) {
228 Arg a = { .i = TargetNew, .s = (char*)webkit_network_request_get_uri(request) };
229 open_arg(&a);
230 webkit_web_policy_decision_ignore(decision);
231 return TRUE;
234 gboolean
235 webview_mimetype_cb(WebKitWebView *webview, WebKitWebFrame *frame, WebKitNetworkRequest *request,
236 char *mime_type, WebKitWebPolicyDecision *decision, gpointer user_data) {
237 if (webkit_web_view_can_show_mime_type(webview, mime_type) == FALSE) {
238 webkit_web_policy_decision_download(decision);
239 return TRUE;
240 } else {
241 return FALSE;
245 static WebKitWebView*
246 inspector_inspect_web_view_cb(gpointer inspector, WebKitWebView* web_view) {
247 gchar* inspector_title;
248 GtkWidget* inspector_window;
249 GtkWidget* inspector_view;
251 /* just enough code to show the inspector - no signal handling etc. */
252 inspector_title = g_strdup_printf("Inspect page - %s - Vimprobable2", webkit_web_view_get_uri(web_view));
253 if (embed) {
254 inspector_window = gtk_plug_new(embed);
255 } else {
256 inspector_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
257 gtk_window_set_wmclass(window, "vimprobable2", "Vimprobable2");
259 gtk_window_set_title(GTK_WINDOW(inspector_window), inspector_title);
260 g_free(inspector_title);
261 inspector_view = webkit_web_view_new();
262 gtk_container_add(GTK_CONTAINER(inspector_window), inspector_view);
263 gtk_widget_show_all(inspector_window);
264 return WEBKIT_WEB_VIEW(inspector_view);
267 gboolean
268 webview_download_cb(WebKitWebView *webview, WebKitDownload *download, gpointer user_data) {
269 const gchar *filename;
270 gchar *uri, *path;
271 uint32_t size;
272 Arg a;
274 filename = webkit_download_get_suggested_filename(download);
275 if (filename == NULL || strlen(filename) == 0) {
276 filename = "vimprobable_download";
278 path = g_build_filename(g_strdup_printf(DOWNLOADS_PATH), filename, NULL);
279 uri = g_strconcat("file://", path, NULL);
280 webkit_download_set_destination_uri(download, uri);
281 g_free(uri);
282 size = (uint32_t)webkit_download_get_total_size(download);
283 a.i = Info;
284 if (size > 0)
285 a.s = g_strdup_printf("Download %s started (expected size: %u bytes)...", filename, size);
286 else
287 a.s = g_strdup_printf("Download %s started (unknown size)...", filename);
288 echo(&a);
289 activeDownloads = g_list_prepend(activeDownloads, download);
290 g_signal_connect(download, "notify::progress", G_CALLBACK(download_progress), NULL);
291 g_signal_connect(download, "notify::status", G_CALLBACK(download_progress), NULL);
292 update_state();
293 return TRUE;
296 void
297 download_progress(WebKitDownload *d, GParamSpec *pspec) {
298 Arg a;
299 WebKitDownloadStatus status = webkit_download_get_status(d);
301 if (status != WEBKIT_DOWNLOAD_STATUS_STARTED && status != WEBKIT_DOWNLOAD_STATUS_CREATED) {
302 if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED) {
303 a.i = Error;
304 a.s = g_strdup_printf("Error while downloading %s", webkit_download_get_suggested_filename(d));
305 echo(&a);
306 } else {
307 a.i = Info;
308 a.s = g_strdup_printf("Download %s finished", webkit_download_get_suggested_filename(d));
309 echo(&a);
311 activeDownloads = g_list_remove(activeDownloads, d);
313 update_state();
317 gboolean
318 process_keypress(GdkEventKey *event) {
319 KeyList *current;
321 current = keylistroot;
322 while (current != NULL) {
323 if (current->Element.mask == CLEAN(event->state)
324 && (current->Element.modkey == current_modkey
325 || (!current->Element.modkey && !current_modkey)
326 || current->Element.modkey == GDK_VoidSymbol ) /* wildcard */
327 && current->Element.key == event->keyval
328 && current->Element.func)
329 if (current->Element.func(&current->Element.arg)) {
330 current_modkey = count = 0;
331 update_state();
332 return TRUE;
334 current = current->next;
336 return FALSE;
339 gboolean
340 webview_keypress_cb(WebKitWebView *webview, GdkEventKey *event) {
341 Arg a = { .i = ModeNormal, .s = NULL };
343 switch (mode) {
344 case ModeNormal:
345 if (CLEAN(event->state) == 0) {
346 memset(inputBuffer, 0, 65);
347 if (event->keyval == GDK_Escape) {
348 a.i = Info;
349 a.s = g_strdup("");
350 echo(&a);
351 } else if (current_modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
352 || (event->keyval == GDK_0 && count))) {
353 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
354 update_state();
355 return TRUE;
356 } else if (strchr(modkeys, event->keyval) && current_modkey != event->keyval) {
357 current_modkey = event->keyval;
358 update_state();
359 return TRUE;
362 /* keybindings */
363 if (process_keypress(event) == TRUE) return TRUE;
365 break;
366 case ModeInsert:
367 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
368 a.i = Silent;
369 a.s = "vimprobable_clearfocus()";
370 script(&a);
371 a.i = ModeNormal;
372 return set(&a);
374 case ModePassThrough:
375 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
376 echo(&a);
377 set(&a);
378 return TRUE;
380 break;
381 case ModeSendKey:
382 echo(&a);
383 set(&a);
384 break;
385 case ModeHints:
386 if (CLEAN(event->state) == 0 && event->keyval == GDK_Escape) {
387 a.i = Silent;
388 a.s = "vimprobable_clear()";
389 script(&a);
390 a.i = ModeNormal;
391 count = 0;
392 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
393 return set(&a);
394 } else if (CLEAN(event->state) == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
395 || (event->keyval >= GDK_KP_1 && event->keyval <= GDK_KP_9)
396 || ((event->keyval == GDK_0 || event->keyval == GDK_KP_0) && count))) {
397 /* allow a zero as non-first number */
398 if (event->keyval >= GDK_KP_0 && event->keyval <= GDK_KP_9)
399 count = (count ? count * 10 : 0) + (event->keyval - GDK_KP_0);
400 else
401 count = (count ? count * 10 : 0) + (event->keyval - GDK_0);
402 memset(inputBuffer, 0, 65);
403 sprintf(inputBuffer, "%d", count);
404 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
405 a.i = Silent;
406 memset(inputBuffer, 0, 65);
407 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
408 script(&a);
409 update_state();
410 return TRUE;
411 } else if ((CLEAN(event->state) == 0 && (event->keyval >= GDK_a && event->keyval <= GDK_z))
412 || (CLEAN(event->state) == GDK_SHIFT_MASK && (event->keyval >= GDK_A && event->keyval <= GDK_Z))
413 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_space && event->keyval <= GDK_slash))
414 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_colon && event->keyval <= GDK_at))
415 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_braceleft && event->keyval <= GDK_umacron))
416 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Babovedot && event->keyval <= GDK_ycircumflex))
417 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_OE && event->keyval <= GDK_Ydiaeresis))
418 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_overline && event->keyval <= GDK_semivoicedsound))
419 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Farsi_0 && event->keyval <= GDK_Arabic_9))
420 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_semicolon && event->keyval <= GDK_Arabic_sukun))
421 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Arabic_madda_above && event->keyval <= GDK_Arabic_heh_goal))
422 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Cyrillic_GHE_bar && event->keyval <= GDK_Cyrillic_u_macron))
423 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Serbian_dje && event->keyval <= GDK_Korean_Won))
424 || ((CLEAN(event->state) == 0 || CLEAN(event->state) == GDK_SHIFT_MASK) && (event->keyval >= GDK_Armenian_ligature_ew && event->keyval <= GDK_braille_dots_12345678))) {
425 /* update hints by link text */
426 if (strlen(inputBuffer) < 65) {
427 memset(inputKey, 0, 5);
428 /* support multibyte characters */
429 sprintf(inputKey, "%C", event->keyval);
430 strncat(inputBuffer, inputKey, 64 - strlen(inputBuffer));
431 /* remember the number of bytes of each character */
432 for (count = 0; count < 64; count++) {
433 if (strncmp((chars + count), "0", 1) == 0) {
434 sprintf(inputKey, "%d", (int)strlen(inputKey));
435 strncpy((chars + count), inputKey, 1);
436 break;
439 memset(inputKey, 0, 5);
440 count = 0;
441 a.i = Silent;
442 a.s = "vimprobable_cleanup()";
443 script(&a);
444 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
445 a.i = Silent;
446 script(&a);
447 update_state();
449 return TRUE;
450 } else if (CLEAN(event->state) == 0 && (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) && count) {
451 memset(inputBuffer, 0, 65);
452 sprintf(inputBuffer, "%d", count);
453 a.s = g_strconcat("vimprobable_fire(", inputBuffer, ")", NULL);
454 a.i = Silent;
455 script(&a);
456 memset(inputBuffer, 0, 65);
457 count = 0;
458 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000\0", 65);
459 update_state();
460 return TRUE;
461 } else if (CLEAN(event->state) == 0 && event->keyval == GDK_BackSpace) {
462 if (count > 9) {
463 count /= 10;
464 memset(inputBuffer, 0, 65);
465 sprintf(inputBuffer, "%d", count);
466 a.s = g_strconcat("vimprobable_update_hints(", inputBuffer, ")", NULL);
467 a.i = Silent;
468 memset(inputBuffer, 0, 65);
469 script(&a);
470 update_state();
471 } else if (count > 0) {
472 count = 0;
473 memset(inputBuffer, 0, 65);
474 a.i = Silent;
475 a.s = "vimprobable_cleanup()";
476 script(&a);
477 a.s = g_strconcat("vimprobable_show_hints()", NULL);
478 a.i = Silent;
479 script(&a);
480 update_state();
481 } else if (strlen(inputBuffer) > 0) {
482 a.i = Silent;
483 a.s = "vimprobable_cleanup()";
484 script(&a);
485 /* check how many bytes the last character uses */
486 for (count = 0; count < 64; count++) {
487 if (strncmp((chars + count), "0", 1) == 0) {
488 break;
491 memset(inputKey, 0, 5);
492 strncpy(inputKey, (chars + count - 1), 1);
493 strncpy((chars + count - 1), "0", 1);
494 count = atoi(inputKey);
495 /* remove the appropriate number of bytes from the string */
496 strncpy((inputBuffer + strlen(inputBuffer) - count), "\0", 1);
497 count = 0;
498 a.s = g_strconcat("vimprobable_show_hints('", inputBuffer, "')", NULL);
499 a.i = Silent;
500 script(&a);
501 update_state();
503 return TRUE;
505 break;
507 return FALSE;
510 void
511 set_widget_font_and_color(GtkWidget *widget, const char *font_str, const char *bg_color_str,
512 const char *fg_color_str) {
513 GdkColor fg_color;
514 GdkColor bg_color;
515 PangoFontDescription *font;
517 font = pango_font_description_from_string(font_str);
518 gtk_widget_modify_font(widget, font);
519 pango_font_description_free(font);
521 if (fg_color_str)
522 gdk_color_parse(fg_color_str, &fg_color);
523 if (bg_color_str)
524 gdk_color_parse(bg_color_str, &bg_color);
526 gtk_widget_modify_text(widget, GTK_STATE_NORMAL, fg_color_str ? &fg_color : NULL);
527 gtk_widget_modify_base(widget, GTK_STATE_NORMAL, bg_color_str ? &bg_color : NULL);
529 return;
532 void
533 webview_hoverlink_cb(WebKitWebView *webview, char *title, char *link, gpointer data) {
534 const char *uri = webkit_web_view_get_uri(webview);
536 memset(rememberedURI, 0, 128);
537 if (link) {
538 gtk_label_set_markup(GTK_LABEL(status_url), g_markup_printf_escaped("<span font=\"%s\">Link: %s</span>", statusfont, link));
539 strncpy(rememberedURI, link, 128);
540 } else
541 update_url(uri);
544 gboolean
545 webview_console_cb(WebKitWebView *webview, char *message, int line, char *source, gpointer user_data) {
546 Arg a;
548 /* Don't change internal mode if the browser doesn't have focus to prevent inconsistent states */
549 if (gtk_window_has_toplevel_focus(window)) {
550 if (!strcmp(message, "hintmode_off") || !strcmp(message, "insertmode_off")) {
551 a.i = ModeNormal;
552 return set(&a);
553 } else if (!strcmp(message, "insertmode_on")) {
554 a.i = ModeInsert;
555 return set(&a);
558 return FALSE;
561 void
562 inputbox_activate_cb(GtkEntry *entry, gpointer user_data) {
563 char *text;
564 guint16 length = gtk_entry_get_text_length(entry);
565 Arg a;
566 int i;
567 size_t len;
568 gboolean success = FALSE, forward = FALSE, found = FALSE;
570 a.i = HideCompletion;
571 complete(&a);
572 if (length < 2)
573 return;
574 text = (char*)gtk_entry_get_text(entry);
575 if (text[0] == ':') {
576 for (i = 0; i < LENGTH(commands); i++) {
577 if (commands[i].cmd == NULL)
578 break;
579 len = strlen(commands[i].cmd);
580 if (length >= len && !strncmp(&text[1], commands[i].cmd, len) && (text[len + 1] == ' ' || !text[len + 1])) {
581 found = TRUE;
582 a.i = commands[i].arg.i;
583 a.s = length > len + 2 ? &text[len + 2] : commands[i].arg.s;
584 success = commands[i].func(&a);
585 break;
589 save_command_history(text);
591 if (!found) {
592 a.i = Error;
593 a.s = g_strdup_printf("Not a browser command: %s", &text[1]);
594 echo(&a);
595 } else if (!success) {
596 a.i = Error;
597 if (error_msg != NULL) {
598 a.s = g_strdup_printf("%s", error_msg);
599 g_free(error_msg);
600 error_msg = NULL;
601 } else {
602 a.s = g_strdup_printf("Unknown error. Please file a bug report!");
604 echo(&a);
606 } else if ((forward = text[0] == '/') || text[0] == '?') {
607 webkit_web_view_unmark_text_matches(webview);
608 #ifdef ENABLE_MATCH_HIGHLITING
609 webkit_web_view_mark_text_matches(webview, &text[1], FALSE, 0);
610 webkit_web_view_set_highlight_text_matches(webview, TRUE);
611 #endif
612 count = 0;
613 #ifndef ENABLE_INCREMENTAL_SEARCH
614 a.s =& text[1];
615 a.i = searchoptions | (forward ? DirectionForward : DirectionBackwards);
616 search(&a);
617 #else
618 search_direction = forward;
619 search_handle = g_strdup(&text[1]);
620 #endif
621 } else
622 return;
623 if (!echo_active)
624 gtk_entry_set_text(entry, "");
625 gtk_widget_grab_focus(GTK_WIDGET(webview));
628 gboolean
629 inputbox_keypress_cb(GtkEntry *entry, GdkEventKey *event) {
630 Arg a;
632 switch (event->keyval) {
633 case GDK_Escape:
634 a.i = HideCompletion;
635 complete(&a);
636 a.i = ModeNormal;
637 return set(&a);
638 break;
639 case GDK_Tab:
640 a.i = DirectionNext;
641 return complete(&a);
642 break;
643 case GDK_Up:
644 a.i = DirectionPrev;
645 return commandhistoryfetch(&a);
646 break;
647 case GDK_Down:
648 a.i = DirectionNext;
649 return commandhistoryfetch(&a);
650 break;
651 case GDK_ISO_Left_Tab:
652 a.i = DirectionPrev;
653 return complete(&a);
654 break;
656 return FALSE;
659 gboolean
660 notify_event_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
661 int i;
662 if (mode == ModeNormal && event->type == GDK_BUTTON_RELEASE) {
663 /* handle mouse click events */
664 for (i = 0; i < LENGTH(mouse); i++) {
665 if (mouse[i].mask == CLEAN(event->button.state)
666 && (mouse[i].modkey == current_modkey
667 || (!mouse[i].modkey && !current_modkey)
668 || mouse[i].modkey == GDK_VoidSymbol) /* wildcard */
669 && mouse[i].button == event->button.button
670 && mouse[i].func) {
671 if (mouse[i].func(&mouse[i].arg)) {
672 current_modkey = count = 0;
673 update_state();
674 return TRUE;
679 return FALSE;
682 static gboolean inputbox_keyrelease_cb(GtkEntry *entry, GdkEventKey *event) {
683 Arg a;
684 guint16 length = gtk_entry_get_text_length(entry);
686 if (!length) {
687 a.i = HideCompletion;
688 complete(&a);
689 a.i = ModeNormal;
690 return set(&a);
692 return FALSE;
695 static gboolean inputbox_changed_cb(GtkEditable *entry, gpointer user_data) {
696 char *text = (char*)gtk_entry_get_text(GTK_ENTRY(entry));
697 guint16 length = gtk_entry_get_text_length(GTK_ENTRY(entry));
698 gboolean forward = FALSE;
700 /* Update incremental search if the user changes the search text.
702 * Note: gtk_widget_is_focus() is a poor way to check if the change comes
703 * from the user. But if the entry is focused and the text is set
704 * through gtk_entry_set_text() in some asyncrounous operation,
705 * I would consider that a bug.
708 if (gtk_widget_is_focus(GTK_WIDGET(entry)) && length > 1 && ((forward = text[0] == '/') || text[0] == '?')) {
709 webkit_web_view_unmark_text_matches(webview);
710 webkit_web_view_search_text(webview, &text[1], searchoptions & CaseSensitive, forward, searchoptions & Wrapping);
711 return TRUE;
714 return FALSE;
717 /* funcs here */
719 void fill_suggline(char * suggline, const char * command, const char *fill_with) {
720 memset(suggline, 0, 512);
721 strncpy(suggline, command, 512);
722 strncat(suggline, " ", 1);
723 strncat(suggline, fill_with, 512 - strlen(suggline) - 1);
726 GtkWidget * fill_eventbox(const char * completion_line) {
727 GtkBox * row;
728 GtkWidget *row_eventbox, *el;
729 GdkColor color;
730 char * markup;
732 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
733 row_eventbox = gtk_event_box_new();
734 gdk_color_parse(completionbgcolor[0], &color);
735 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
736 el = gtk_label_new(NULL);
737 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">",
738 g_markup_escape_text(completion_line, strlen(completion_line)), "</span>", NULL);
739 gtk_label_set_markup(GTK_LABEL(el), markup);
740 g_free(markup);
741 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
742 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
743 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
744 return row_eventbox;
747 gboolean
748 complete(const Arg *arg) {
749 char *str, *p, *s, *markup, *entry, *searchfor, command[32] = "", suggline[512] = "", **suggurls;
750 size_t listlen, len, cmdlen;
751 int i, spacepos;
752 Listelement *elementlist = NULL, *elementpointer;
753 gboolean highlight = FALSE;
754 GtkBox *row;
755 GtkWidget *row_eventbox, *el;
756 GtkBox *_table;
757 GdkColor color;
758 static GtkWidget *table, **widgets, *top_border;
759 static char **suggestions, *prefix;
760 static int n = 0, m, current = -1;
762 str = (char*)gtk_entry_get_text(GTK_ENTRY(inputbox));
763 len = strlen(str);
764 if ((len == 0 || str[0] != ':') && arg->i != HideCompletion)
765 return TRUE;
766 if (prefix) {
767 if (arg->i != HideCompletion && widgets && current != -1 && !strcmp(&str[1], suggestions[current])) {
768 gdk_color_parse(completionbgcolor[0], &color);
769 gtk_widget_modify_bg(widgets[current], GTK_STATE_NORMAL, &color);
770 current = (n + current + (arg->i == DirectionPrev ? -1 : 1)) % n;
771 if ((arg->i == DirectionNext && current == 0)
772 || (arg->i == DirectionPrev && current == n - 1))
773 current = -1;
774 } else {
775 free(widgets);
776 free(suggestions);
777 free(prefix);
778 gtk_widget_destroy(GTK_WIDGET(table));
779 gtk_widget_destroy(GTK_WIDGET(top_border));
780 table = NULL;
781 widgets = NULL;
782 suggestions = NULL;
783 prefix = NULL;
784 n = 0;
785 current = -1;
786 if (arg->i == HideCompletion)
787 return TRUE;
789 } else if (arg->i == HideCompletion)
790 return TRUE;
791 if (!widgets) {
792 prefix = g_strdup_printf(str);
793 widgets = malloc(sizeof(GtkWidget*) * MAX_LIST_SIZE);
794 suggestions = malloc(sizeof(char*) * MAX_LIST_SIZE);
795 top_border = gtk_event_box_new();
796 gtk_widget_set_size_request(GTK_WIDGET(top_border), 0, 1);
797 gdk_color_parse(completioncolor[2], &color);
798 gtk_widget_modify_bg(top_border, GTK_STATE_NORMAL, &color);
799 table = gtk_event_box_new();
800 gdk_color_parse(completionbgcolor[0], &color);
801 _table = GTK_BOX(gtk_vbox_new(FALSE, 0));
802 highlight = len > 1;
803 if (strchr(str, ' ') == NULL) {
804 /* command completion */
805 listlen = LENGTH(commands);
806 for (i = 0; i < listlen; i++) {
807 if (commands[i].cmd == NULL)
808 break;
809 cmdlen = strlen(commands[i].cmd);
810 if (!highlight || (n < MAX_LIST_SIZE && len - 1 <= cmdlen && !strncmp(&str[1], commands[i].cmd, len - 1))) {
811 p = s = malloc(sizeof(char*) * (highlight ? sizeof(COMPLETION_TAG_OPEN) + sizeof(COMPLETION_TAG_CLOSE) - 1 : 1) + cmdlen);
812 if (highlight) {
813 memcpy(p, COMPLETION_TAG_OPEN, sizeof(COMPLETION_TAG_OPEN) - 1);
814 memcpy((p += sizeof(COMPLETION_TAG_OPEN) - 1), &str[1], len - 1);
815 memcpy((p += len - 1), COMPLETION_TAG_CLOSE, sizeof(COMPLETION_TAG_CLOSE) - 1);
816 p += sizeof(COMPLETION_TAG_CLOSE) - 1;
818 memcpy(p, &commands[i].cmd[len - 1], cmdlen - len + 2);
819 row = GTK_BOX(gtk_hbox_new(FALSE, 0));
820 row_eventbox = gtk_event_box_new();
821 gtk_widget_modify_bg(row_eventbox, GTK_STATE_NORMAL, &color);
822 el = gtk_label_new(NULL);
823 markup = g_strconcat("<span font=\"", completionfont[0], "\" color=\"", completioncolor[0], "\">", s, "</span>", NULL);
824 free(s);
825 gtk_label_set_markup(GTK_LABEL(el), markup);
826 g_free(markup);
827 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
828 gtk_box_pack_start(row, el, TRUE, TRUE, 2);
829 gtk_container_add(GTK_CONTAINER(row_eventbox), GTK_WIDGET(row));
830 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
831 suggestions[n] = commands[i].cmd;
832 widgets[n++] = row_eventbox;
835 } else {
836 entry = (char *)malloc(512 * sizeof(char));
837 if (entry == NULL) {
838 return FALSE;
840 memset(entry, 0, 512);
841 suggurls = malloc(sizeof(char*) * MAX_LIST_SIZE);
842 if (suggurls == NULL) {
843 return FALSE;
845 spacepos = strcspn(str, " ");
846 searchfor = (str + spacepos + 1);
847 strncpy(command, (str + 1), spacepos - 1);
848 if (strlen(command) == 3 && strncmp(command, "set", 3) == 0) {
849 /* browser settings */
850 listlen = LENGTH(browsersettings);
851 for (i = 0; i < listlen; i++) {
852 if (n < MAX_LIST_SIZE && strstr(browsersettings[i].name, searchfor) != NULL) {
853 /* match */
854 fill_suggline(suggline, command, browsersettings[i].name);
855 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
856 strncpy(suggurls[n], suggline, 512);
857 suggestions[n] = suggurls[n];
858 row_eventbox = fill_eventbox(suggline);
859 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
860 widgets[n++] = row_eventbox;
864 } else if (strlen(command) == 2 && strncmp(command, "qt", 2) == 0) {
865 /* completion on tags */
866 spacepos = strcspn(str, " ");
867 searchfor = (str + spacepos + 1);
868 elementlist = complete_list(searchfor, 1, elementlist);
869 } else {
870 /* URL completion: bookmarks */
871 elementlist = complete_list(searchfor, 0, elementlist);
872 m = count_list(elementlist);
873 if (m < MAX_LIST_SIZE) {
874 /* URL completion: history */
875 elementlist = complete_list(searchfor, 2, elementlist);
878 elementpointer = elementlist;
879 while (elementpointer != NULL) {
880 fill_suggline(suggline, command, elementpointer->element);
881 suggurls[n] = (char *)malloc(sizeof(char) * 512 + 1);
882 strncpy(suggurls[n], suggline, 512);
883 suggestions[n] = suggurls[n];
884 row_eventbox = fill_eventbox(suggline);
885 gtk_box_pack_start(_table, GTK_WIDGET(row_eventbox), FALSE, FALSE, 0);
886 widgets[n++] = row_eventbox;
887 elementpointer = elementpointer->next;
888 if (n >= MAX_LIST_SIZE)
889 break;
891 free_list(elementlist);
892 if (suggurls != NULL) {
893 free(suggurls);
894 suggurls = NULL;
896 if (entry != NULL) {
897 free(entry);
898 entry = NULL;
901 widgets = realloc(widgets, sizeof(GtkWidget*) * n);
902 suggestions = realloc(suggestions, sizeof(char*) * n);
903 if (!n) {
904 gdk_color_parse(completionbgcolor[1], &color);
905 gtk_widget_modify_bg(table, GTK_STATE_NORMAL, &color);
906 el = gtk_label_new(NULL);
907 gtk_misc_set_alignment(GTK_MISC(el), 0, 0);
908 markup = g_strconcat("<span font=\"", completionfont[1], "\" color=\"", completioncolor[1], "\">No Completions</span>", NULL);
909 gtk_label_set_markup(GTK_LABEL(el), markup);
910 g_free(markup);
911 gtk_box_pack_start(_table, GTK_WIDGET(el), FALSE, FALSE, 0);
913 gtk_box_pack_start(box, GTK_WIDGET(top_border), FALSE, FALSE, 0);
914 gtk_container_add(GTK_CONTAINER(table), GTK_WIDGET(_table));
915 gtk_box_pack_start(box, GTK_WIDGET(table), FALSE, FALSE, 0);
916 gtk_widget_show_all(GTK_WIDGET(window));
917 if (!n)
918 return TRUE;
919 current = arg->i == DirectionPrev ? n - 1 : 0;
921 if (current != -1) {
922 gdk_color_parse(completionbgcolor[2], &color);
923 gtk_widget_modify_bg(GTK_WIDGET(widgets[current]), GTK_STATE_NORMAL, &color);
924 s = g_strconcat(":", suggestions[current], NULL);
925 gtk_entry_set_text(GTK_ENTRY(inputbox), s);
926 g_free(s);
927 } else
928 gtk_entry_set_text(GTK_ENTRY(inputbox), prefix);
929 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
930 return TRUE;
933 gboolean
934 descend(const Arg *arg) {
935 char *source = (char*)webkit_web_view_get_uri(webview), *p = &source[0], *new;
936 int i, len;
937 count = count ? count : 1;
939 if (!source)
940 return TRUE;
941 if (arg->i == Rootdir) {
942 for (i = 0; i < 3; i++) /* get to the third slash */
943 if (!(p = strchr(++p, '/')))
944 return TRUE; /* if we cannot find it quit */
945 } else {
946 len = strlen(source);
947 if (!len) /* if string is empty quit */
948 return TRUE;
949 p = source + len; /* start at the end */
950 if (*(p - 1) == '/') /* /\/$/ is not an additional level */
951 ++count;
952 for (i = 0; i < count; i++)
953 while(*(p--) != '/' || *p == '/') /* count /\/+/ as one slash */
954 if (p == source) /* if we reach the first char pointer quit */
955 return TRUE;
956 ++p; /* since we do p-- in the while, we are pointing at
957 the char before the slash, so +1 */
959 len = p - source + 1; /* new length = end - start + 1 */
960 new = malloc(len + 1);
961 memcpy(new, source, len);
962 new[len] = '\0';
963 webkit_web_view_load_uri(webview, new);
964 free(new);
965 return TRUE;
968 gboolean
969 echo(const Arg *arg) {
970 int index = !arg->s ? 0 : arg->i & (~NoAutoHide);
972 if (index < Info || index > Error)
973 return TRUE;
975 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
976 gtk_entry_set_text(GTK_ENTRY(inputbox), !arg->s ? "" : arg->s);
978 /* TA: Always free arg->s here, rather than relying on the caller to do
979 * this.
981 if (arg->s)
982 g_free(arg->s);
984 return TRUE;
987 gboolean
988 input(const Arg *arg) {
989 int pos = 0;
990 count = 0;
991 const char *url;
992 int index = Info;
994 update_state();
996 /* Set the colour and font back to the default, so that we don't still
997 * maintain a red colour from a warning from an end of search indicator,
998 * etc.
1000 set_widget_font_and_color(inputbox, urlboxfont[index], urlboxbgcolor[index], urlboxcolor[index]);
1002 /* to avoid things like :open URL :open URL2 or :open :open URL */
1003 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1004 gtk_editable_insert_text(GTK_EDITABLE(inputbox), arg->s, -1, &pos);
1005 if (arg->i & InsertCurrentURL && (url = webkit_web_view_get_uri(webview)))
1006 gtk_editable_insert_text(GTK_EDITABLE(inputbox), url, -1, &pos);
1007 gtk_widget_grab_focus(inputbox);
1008 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1009 return TRUE;
1012 gboolean
1013 navigate(const Arg *arg) {
1014 if (arg->i & NavigationForwardBack)
1015 webkit_web_view_go_back_or_forward(webview, (arg->i == NavigationBack ? -1 : 1) * (count ? count : 1));
1016 else if (arg->i & NavigationReloadActions)
1017 (arg->i == NavigationReload ? webkit_web_view_reload : webkit_web_view_reload_bypass_cache)(webview);
1018 else
1019 webkit_web_view_stop_loading(webview);
1020 return TRUE;
1023 gboolean
1024 number(const Arg *arg) {
1025 const char *source = webkit_web_view_get_uri(webview);
1026 char *uri, *p, *new;
1027 int number, diff = (count ? count : 1) * (arg->i == Increment ? 1 : -1);
1029 if (!source)
1030 return TRUE;
1031 uri = g_strdup_printf(source); /* copy string */
1032 p =& uri[0];
1033 while(*p != '\0') /* goto the end of the string */
1034 ++p;
1035 --p;
1036 while(*p >= '0' && *p <= '9') /* go back until non number char is reached */
1037 --p;
1038 if (*(++p) == '\0') { /* if no numbers were found abort */
1039 free(uri);
1040 return TRUE;
1042 number = atoi(p) + diff; /* apply diff on number */
1043 *p = '\0';
1044 new = g_strdup_printf("%s%d", uri, number); /* create new uri */
1045 webkit_web_view_load_uri(webview, new);
1046 g_free(new);
1047 free(uri);
1048 return TRUE;
1051 gboolean
1052 open_arg(const Arg *arg) {
1053 char *argv[6];
1054 char *s = arg->s, *p, *new;
1055 Arg a = { .i = NavigationReload };
1056 int len, i;
1058 if (embed) {
1059 argv[0] = *args;
1060 argv[1] = "-e";
1061 argv[2] = winid;
1062 argv[3] = arg->s;
1063 argv[4] = NULL;
1064 } else {
1065 argv[0] = *args;
1066 argv[1] = arg->s;
1067 argv[2] = NULL;
1070 if (!arg->s)
1071 navigate(&a);
1072 else if (arg->i == TargetCurrent) {
1073 len = strlen(arg->s);
1074 new = NULL, p = strchr(arg->s, ' ');
1075 if (p) /* check for search engines */
1076 for (i = 0; i < LENGTH(searchengines); i++)
1077 if (!strncmp(arg->s, searchengines[i].handle, p - arg->s)) {
1078 p = soup_uri_encode(++p, "&");
1079 new = g_strdup_printf(searchengines[i].uri, p);
1080 g_free(p);
1081 break;
1083 if (!new) {
1084 if (len > 3 && strstr(arg->s, "://")) { /* valid url? */
1085 p = new = g_malloc(len + 1);
1086 while(*s != '\0') { /* strip whitespaces */
1087 if (*s != ' ')
1088 *(p++) = *s;
1089 ++s;
1091 *p = '\0';
1092 } else if (strcspn(arg->s, "/") == 0 || strcspn(arg->s, "./") == 0) { /* prepend "file://" */
1093 new = g_malloc(sizeof("file://") + len);
1094 strcpy(new, "file://");
1095 memcpy(&new[sizeof("file://") - 1], arg->s, len + 1);
1096 } else if (p || !strchr(arg->s, '.')) { /* whitespaces or no dot? */
1097 p = soup_uri_encode(arg->s, "&");
1098 new = g_strdup_printf(defsearch->uri, p);
1099 g_free(p);
1100 } else { /* prepend "http://" */
1101 new = g_malloc(sizeof("http://") + len);
1102 strcpy(new, "http://");
1103 memcpy(&new[sizeof("http://") - 1], arg->s, len + 1);
1106 webkit_web_view_load_uri(webview, new);
1107 g_free(new);
1108 } else
1109 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1110 return TRUE;
1113 gboolean
1114 yank(const Arg *arg) {
1115 const char *url, *feedback;
1117 if (arg->i & SourceURL) {
1118 url = webkit_web_view_get_uri(webview);
1119 if (!url)
1120 return TRUE;
1121 feedback = g_strconcat("Yanked ", url, NULL);
1122 give_feedback(feedback);
1123 if (arg->i & ClipboardPrimary)
1124 gtk_clipboard_set_text(clipboards[0], url, -1);
1125 if (arg->i & ClipboardGTK)
1126 gtk_clipboard_set_text(clipboards[1], url, -1);
1127 } else
1128 webkit_web_view_copy_clipboard(webview);
1129 return TRUE;
1132 gboolean
1133 paste(const Arg *arg) {
1134 Arg a = { .i = arg->i & TargetNew, .s = NULL };
1136 /* If we're over a link, open it in a new target. */
1137 if (strlen(rememberedURI) > 0) {
1138 Arg new_target = { .i = TargetNew, .s = arg->s };
1139 open_arg(&new_target);
1140 return TRUE;
1143 if (arg->i & ClipboardPrimary)
1144 a.s = gtk_clipboard_wait_for_text(clipboards[0]);
1145 if (!a.s && arg->i & ClipboardGTK)
1146 a.s = gtk_clipboard_wait_for_text(clipboards[1]);
1147 if (a.s)
1148 open_arg(&a);
1149 return TRUE;
1152 gboolean
1153 quit(const Arg *arg) {
1154 FILE *f;
1155 const char *filename;
1156 const char *uri = webkit_web_view_get_uri(webview);
1157 if (uri != NULL) {
1158 /* write last URL into status file for recreation with "u" */
1159 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1160 f = fopen(filename, "w");
1161 if (f != NULL) {
1162 fprintf(f, "%s", uri);
1163 fclose(f);
1166 gtk_main_quit();
1167 return TRUE;
1170 gboolean
1171 revive(const Arg *arg) {
1172 FILE *f;
1173 const char *filename;
1174 char buffer[512] = "";
1175 Arg a = { .i = TargetNew, .s = NULL };
1176 /* get the URL of the window which has been closed last */
1177 filename = g_strdup_printf(CLOSED_URL_FILENAME);
1178 f = fopen(filename, "r");
1179 if (f != NULL) {
1180 fgets(buffer, 512, f);
1181 fclose(f);
1183 if (strlen(buffer) > 0) {
1184 a.s = buffer;
1185 open_arg(&a);
1186 return TRUE;
1188 return FALSE;
1191 static
1192 gboolean print_frame(const Arg *arg)
1194 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1195 webkit_web_frame_print (frame);
1196 return TRUE;
1199 gboolean
1200 search(const Arg *arg) {
1201 count = count ? count : 1;
1202 gboolean success, direction = arg->i & DirectionPrev;
1203 Arg a;
1205 if (arg->s) {
1206 free(search_handle);
1207 search_handle = g_strdup_printf(arg->s);
1209 if (!search_handle)
1210 return TRUE;
1211 if (arg->i & DirectionAbsolute)
1212 search_direction = direction;
1213 else
1214 direction ^= search_direction;
1215 do {
1216 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, FALSE);
1217 if (!success) {
1218 if (arg->i & Wrapping) {
1219 success = webkit_web_view_search_text(webview, search_handle, arg->i & CaseSensitive, direction, TRUE);
1220 if (success) {
1221 a.i = Warning;
1222 a.s = g_strdup_printf("search hit %s, continuing at %s",
1223 direction ? "BOTTOM" : "TOP",
1224 direction ? "TOP" : "BOTTOM");
1225 echo(&a);
1226 } else
1227 break;
1228 } else
1229 break;
1231 } while(--count);
1232 if (!success) {
1233 a.i = Error;
1234 a.s = g_strdup_printf("Pattern not found: %s", search_handle);
1235 echo(&a);
1237 return TRUE;
1240 gboolean
1241 set(const Arg *arg) {
1242 Arg a = { .i = Info | NoAutoHide };
1244 switch (arg->i) {
1245 case ModeNormal:
1246 if (search_handle) {
1247 search_handle = NULL;
1248 webkit_web_view_unmark_text_matches(webview);
1250 gtk_entry_set_text(GTK_ENTRY(inputbox), "");
1251 gtk_widget_grab_focus(GTK_WIDGET(webview));
1252 break;
1253 case ModePassThrough:
1254 a.s = g_strdup("-- PASS THROUGH --");
1255 echo(&a);
1256 break;
1257 case ModeSendKey:
1258 a.s = g_strdup("-- PASS TROUGH (next) --");
1259 echo(&a);
1260 break;
1261 case ModeInsert: /* should not be called manually but automatically */
1262 a.s = g_strdup("-- INSERT --");
1263 echo(&a);
1264 break;
1265 case ModeHints:
1266 memset(followTarget, 0, 8);
1267 strncpy(followTarget, arg->s, 8);
1268 a.i = Silent;
1269 a.s = "vimprobable_show_hints()";
1270 script(&a);
1271 break;
1272 default:
1273 return TRUE;
1275 mode = arg->i;
1276 return TRUE;
1279 gchar*
1280 jsapi_ref_to_string(JSContextRef context, JSValueRef ref) {
1281 JSStringRef string_ref;
1282 gchar *string;
1283 size_t length;
1285 string_ref = JSValueToStringCopy(context, ref, NULL);
1286 length = JSStringGetMaximumUTF8CStringSize(string_ref);
1287 string = g_new(gchar, length);
1288 JSStringGetUTF8CString(string_ref, string, length);
1289 JSStringRelease(string_ref);
1290 return string;
1293 void
1294 jsapi_evaluate_script(const gchar *script, gchar **value, gchar **message) {
1295 WebKitWebFrame *frame = webkit_web_view_get_main_frame(webview);
1296 JSGlobalContextRef context = webkit_web_frame_get_global_context(frame);
1297 JSStringRef str;
1298 JSValueRef val, exception;
1300 str = JSStringCreateWithUTF8CString(script);
1301 val = JSEvaluateScript(context, str, JSContextGetGlobalObject(context), NULL, 0, &exception);
1302 JSStringRelease(str);
1303 if (!val)
1304 *message = jsapi_ref_to_string(context, exception);
1305 else
1306 *value = jsapi_ref_to_string(context, val);
1309 gboolean
1310 quickmark(const Arg *a) {
1311 int i, b;
1312 b = atoi(a->s);
1313 char *fn = g_strdup_printf(QUICKMARK_FILE);
1314 FILE *fp;
1315 fp = fopen(fn, "r");
1316 char buf[100];
1318 if (fp != NULL && b < 10) {
1319 for( i=0; i < b; ++i ) {
1320 if (feof(fp)) {
1321 break;
1323 fgets(buf, 100, fp);
1325 char *ptr = strrchr(buf, '\n');
1326 *ptr = '\0';
1327 Arg x = { .s = buf };
1328 if ( strlen(buf)) return open_arg(&x);
1329 else
1331 x.i = Error;
1332 x.s = g_strdup_printf("Quickmark %d not defined", b);
1333 echo(&x);
1334 return false;
1337 else { return false; }
1340 gboolean
1341 script(const Arg *arg) {
1342 gchar *value = NULL, *message = NULL;
1343 Arg a;
1345 if (!arg->s) {
1346 set_error("Missing argument.");
1347 return FALSE;
1349 jsapi_evaluate_script(arg->s, &value, &message);
1350 if (message) {
1351 set_error(message);
1352 return FALSE;
1354 if (arg->i != Silent && value) {
1355 a.i = arg->i;
1356 a.s = g_strdup(value);
1357 echo(&a);
1359 if (value) {
1360 if (strncmp(value, "fire;", 5) == 0) {
1361 count = 0;
1362 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1363 memset(inputBuffer, 0, 65);
1364 a.s = g_strconcat("vimprobable_fire(", (value + 5), ")", NULL);
1365 a.i = Silent;
1366 script(&a);
1367 } else if (strncmp(value, "open;", 5) == 0) {
1368 count = 0;
1369 strncpy(chars, "0000000000000000000000000000000000000000000000000000000000000000", 64);
1370 memset(inputBuffer, 0, 65);
1371 a.i = ModeNormal;
1372 set(&a);
1373 if (strncmp(followTarget, "new", 3) == 0)
1374 a.i = TargetNew;
1375 else
1376 a.i = TargetCurrent;
1377 memset(followTarget, 0, 8);
1378 a.s = (value + 5);
1379 open_arg(&a);
1382 g_free(value);
1383 return TRUE;
1386 gboolean
1387 scroll(const Arg *arg) {
1388 GtkAdjustment *adjust = (arg->i & OrientationHoriz) ? adjust_h : adjust_v;
1389 int max = gtk_adjustment_get_upper(adjust) - gtk_adjustment_get_page_size(adjust);
1390 float val = gtk_adjustment_get_value(adjust) / max * 100;
1391 int direction = (arg->i & (1 << 2)) ? 1 : -1;
1393 if ((direction == 1 && val < 100) || (direction == -1 && val > 0)) {
1394 if (arg->i & ScrollMove)
1395 gtk_adjustment_set_value(adjust, gtk_adjustment_get_value(adjust) +
1396 direction * /* direction */
1397 ((arg->i & UnitLine || (arg->i & UnitBuffer && count)) ? (scrollstep * (count ? count : 1)) : (
1398 arg->i & UnitBuffer ? gtk_adjustment_get_page_size(adjust) / 2 :
1399 (count ? count : 1) * (gtk_adjustment_get_page_size(adjust) -
1400 (gtk_adjustment_get_page_size(adjust) > pagingkeep ? pagingkeep : 0)))));
1401 else
1402 gtk_adjustment_set_value(adjust,
1403 ((direction == 1) ? gtk_adjustment_get_upper : gtk_adjustment_get_lower)(adjust));
1404 update_state();
1406 return TRUE;
1409 gboolean
1410 zoom(const Arg *arg) {
1411 webkit_web_view_set_full_content_zoom(webview, (arg->i & ZoomFullContent) > 0);
1412 webkit_web_view_set_zoom_level(webview, (arg->i & ZoomOut) ?
1413 webkit_web_view_get_zoom_level(webview) +
1414 (((float)(count ? count : 1)) * (arg->i & (1 << 1) ? 1.0 : -1.0) * zoomstep) :
1415 (count ? (float)count / 100.0 : 1.0));
1416 return TRUE;
1419 gboolean
1420 fake_key_event(const Arg *a) {
1421 if(!embed) {
1422 return FALSE;
1424 Arg err;
1425 err.i = Error;
1426 Display *xdpy;
1427 if ( (xdpy = XOpenDisplay(NULL)) == NULL ) {
1428 err.s = g_strdup("Couldn't find the XDisplay.");
1429 echo(&err);
1430 return FALSE;
1433 XKeyEvent xk;
1434 xk.display = xdpy;
1435 xk.subwindow = None;
1436 xk.time = CurrentTime;
1437 xk.same_screen = True;
1438 xk.x = xk.y = xk.x_root = xk.y_root = 1;
1439 xk.window = embed;
1440 xk.state = a->i;
1442 if( ! a->s ) {
1443 err.s = g_strdup("Zero pointer as argument! Check your config.h");
1444 echo(&err);
1445 return FALSE;
1448 KeySym keysym;
1449 if( (keysym = XStringToKeysym(a->s)) == NoSymbol ) {
1450 err.s = g_strdup_printf("Couldn't translate %s to keysym", a->s );
1451 echo(&err);
1452 return FALSE;
1455 if( (xk.keycode = XKeysymToKeycode(xdpy, keysym)) == NoSymbol ) {
1456 err.s = g_strdup("Couldn't translate keysym to keycode");
1457 echo(&err);
1458 return FALSE;
1461 xk.type = KeyPress;
1462 if( !XSendEvent(xdpy, embed, True, KeyPressMask, (XEvent *)&xk) ) {
1463 err.s = g_strdup("XSendEvent failed");
1464 echo(&err);
1465 return FALSE;
1467 XFlush(xdpy);
1469 return TRUE;
1473 gboolean
1474 commandhistoryfetch(const Arg *arg) {
1475 if (arg->i == DirectionPrev) {
1476 commandpointer--;
1477 if (commandpointer < 0)
1478 commandpointer = maxcommands - 1;
1479 } else {
1480 commandpointer++;
1481 if (commandpointer == COMMANDHISTSIZE || commandpointer == maxcommands)
1482 commandpointer = 0;
1485 if (commandpointer < 0)
1486 return FALSE;
1488 gtk_entry_set_text(GTK_ENTRY(inputbox), commandhistory[commandpointer ]);
1489 gtk_editable_set_position(GTK_EDITABLE(inputbox), -1);
1490 return TRUE;
1494 gboolean
1495 bookmark(const Arg *arg) {
1496 FILE *f;
1497 const char *filename;
1498 const char *uri = webkit_web_view_get_uri(webview);
1499 const char *title = webkit_web_view_get_title(webview);
1500 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1501 f = fopen(filename, "a");
1502 if (uri == NULL || strlen(uri) == 0) {
1503 set_error("No URI found to bookmark.");
1504 return FALSE;
1506 if (f != NULL) {
1507 fprintf(f, "%s", uri);
1508 if (title != NULL) {
1509 fprintf(f, "%s", " ");
1510 fprintf(f, "%s", title);
1512 if (arg->s && strlen(arg->s)) {
1513 build_taglist(arg, f);
1515 fprintf(f, "%s", "\n");
1516 fclose(f);
1517 give_feedback( "Bookmark saved" );
1518 return TRUE;
1519 } else {
1520 set_error("Bookmarks file not found.");
1521 return FALSE;
1525 gboolean
1526 history() {
1527 FILE *f;
1528 const char *filename;
1529 const char *uri = webkit_web_view_get_uri(webview);
1530 const char *title = webkit_web_view_get_title(webview);
1531 char *entry, buffer[512], *new;
1532 int n, i = 0;
1533 gboolean finished = FALSE;
1534 if (uri != NULL) {
1535 if (title != NULL) {
1536 entry = malloc((strlen(uri) + strlen(title) + 2) * sizeof(char));
1537 memset(entry, 0, strlen(uri) + strlen(title) + 2);
1538 } else {
1539 entry = malloc((strlen(uri) + 1) * sizeof(char));
1540 memset(entry, 0, strlen(uri) + 1);
1542 if (entry != NULL) {
1543 strncpy(entry, uri, strlen(uri));
1544 if (title != NULL) {
1545 strncat(entry, " ", 1);
1546 strncat(entry, title, strlen(title));
1548 n = strlen(entry);
1549 filename = g_strdup_printf(HISTORY_STORAGE_FILENAME);
1550 f = fopen(filename, "r");
1551 if (f != NULL) {
1552 new = (char *)malloc(HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1553 if (new != NULL) {
1554 memset(new, 0, HISTORY_MAX_ENTRIES * 512 * sizeof(char) + 1);
1555 /* newest entries go on top */
1556 strncpy(new, entry, strlen(entry));
1557 strncat(new, "\n", 1);
1558 /* retain at most HISTORY_MAX_ENTIRES - 1 old entries */
1559 while (finished != TRUE) {
1560 if ((char *)NULL == fgets(buffer, 512, f)) {
1561 /* check if end of file was reached / error occured */
1562 if (!feof(f)) {
1563 break;
1565 /* end of file reached */
1566 finished = TRUE;
1567 continue;
1569 /* compare line (-1 because of newline character) */
1570 if (n != strlen(buffer) - 1 || strncmp(entry, buffer, n) != 0) {
1571 /* if the URI is already in history; we put it on top and skip it here */
1572 strncat(new, buffer, 512);
1573 i++;
1575 if ((i + 1) >= HISTORY_MAX_ENTRIES) {
1576 break;
1579 fclose(f);
1581 f = fopen(filename, "w");
1582 if (f != NULL) {
1583 fprintf(f, "%s", new);
1584 fclose(f);
1586 if (new != NULL) {
1587 free(new);
1588 new = NULL;
1592 if (entry != NULL) {
1593 free(entry);
1594 entry = NULL;
1597 return TRUE;
1600 static gboolean
1601 view_source(const Arg * arg) {
1602 gboolean current_mode = webkit_web_view_get_view_source_mode(webview);
1603 webkit_web_view_set_view_source_mode(webview, !current_mode);
1604 webkit_web_view_reload(webview);
1605 return TRUE;
1608 static gboolean
1609 focus_input(const Arg *arg) {
1610 static Arg a;
1612 a.s = g_strconcat("vimprobable_focus_input()", NULL);
1613 a.i = Silent;
1614 script(&a);
1615 update_state();
1616 return TRUE;
1619 static gboolean
1620 browser_settings(const Arg *arg) {
1621 char line[255];
1622 if (!arg->s) {
1623 set_error("Missing argument.");
1624 return FALSE;
1626 strncpy(line, arg->s, 254);
1627 if (process_set_line(line))
1628 return TRUE;
1629 else {
1630 set_error("Invalid setting.");
1631 return FALSE;
1635 char *
1636 search_word(int whichword) {
1637 int k = 0;
1638 static char word[240];
1639 char *c = my_pair.line;
1641 while (isspace(*c) && *c)
1642 c++;
1644 switch (whichword) {
1645 case 0:
1646 while (*c && !isspace (*c) && *c != '=' && k < 240) {
1647 word[k++] = *c;
1648 c++;
1650 word[k] = '\0';
1651 strncpy(my_pair.what, word, 20);
1652 break;
1653 case 1:
1654 while (*c && k < 240) {
1655 word[k++] = *c;
1656 c++;
1658 word[k] = '\0';
1659 strncpy(my_pair.value, word, 240);
1660 break;
1663 return c;
1666 static gboolean
1667 process_set_line(char *line) {
1668 char *c;
1669 int listlen, i;
1670 gboolean boolval;
1671 WebKitWebSettings *settings;
1673 settings = webkit_web_view_get_settings(webview);
1674 my_pair.line = line;
1675 c = search_word(0);
1676 if (!strlen(my_pair.what))
1677 return FALSE;
1679 while (isspace(*c) && *c)
1680 c++;
1682 if (*c == ':' || *c == '=')
1683 c++;
1685 my_pair.line = c;
1686 c = search_word(1);
1688 listlen = LENGTH(browsersettings);
1689 for (i = 0; i < listlen; i++) {
1690 if (strlen(browsersettings[i].name) == strlen(my_pair.what) && strncmp(browsersettings[i].name, my_pair.what, strlen(my_pair.what)) == 0) {
1691 /* mandatory argument not provided */
1692 if (strlen(my_pair.value) == 0)
1693 return FALSE;
1694 /* process qmark? */
1695 if (strlen(my_pair.what) == 5 && strncmp("qmark", my_pair.what, 5) == 0) {
1696 return (process_save_qmark(my_pair.value, webview));
1698 /* interpret boolean values */
1699 if (browsersettings[i].boolval) {
1700 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) {
1701 boolval = TRUE;
1702 } 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) {
1703 boolval = FALSE;
1704 } else {
1705 return FALSE;
1707 } else if (browsersettings[i].colourval) {
1708 /* interpret as hexadecimal colour */
1709 if (!parse_colour(my_pair.value)) {
1710 return FALSE;
1713 if (browsersettings[i].var != NULL) {
1714 /* write value into internal variable */
1715 /*if (browsersettings[i].intval) {
1716 browsersettings[i].var = atoi(my_pair.value);
1717 } else {*/
1718 strncpy(browsersettings[i].var, my_pair.value, strlen(my_pair.value) + 1);
1719 /*}*/
1721 if (strlen(browsersettings[i].webkit) > 0) {
1722 /* activate appropriate webkit setting */
1723 if (browsersettings[i].boolval) {
1724 g_object_set((GObject*)settings, browsersettings[i].webkit, boolval, NULL);
1725 } else if (browsersettings[i].intval) {
1726 g_object_set((GObject*)settings, browsersettings[i].webkit, atoi(my_pair.value), NULL);
1727 } else {
1728 g_object_set((GObject*)settings, browsersettings[i].webkit, my_pair.value, NULL);
1730 webkit_web_view_set_settings(webview, settings);
1732 /* toggle proxy usage? */
1733 if (strlen(my_pair.what) == 5 && strncmp("proxy", my_pair.what, 5) == 0) {
1734 toggle_proxy(boolval);
1737 /* Toggle scrollbars. */
1738 if (strlen(my_pair.what) == 10 && strncmp("scrollbars", my_pair.what, 10) == 0)
1739 toggle_scrollbars(boolval);
1741 /* case sensitivity of completion */
1742 if (strlen(my_pair.what) == 14 && strncmp("completioncase", my_pair.what, 14) == 0)
1743 complete_case_sensitive = boolval;
1745 /* reload page? */
1746 if (browsersettings[i].reload)
1747 webkit_web_view_reload(webview);
1748 return TRUE;
1751 return FALSE;
1754 gboolean
1755 process_line(char *line) {
1756 char *c = line;
1758 while (isspace(*c))
1759 c++;
1760 /* Ignore blank lines. */
1761 if (c[0] == '\0')
1762 return TRUE;
1763 if (strncmp(c, "map", 3) == 0) {
1764 c += 4;
1765 return process_map_line(c);
1766 } else if (strncmp(c, "set", 3) == 0) {
1767 c += 4;
1768 return process_set_line(c);
1770 return FALSE;
1773 static gboolean
1774 search_tag(const Arg * a) {
1775 FILE *f;
1776 const char *filename;
1777 const char *tag = a->s;
1778 char s[BUFFERSIZE], foundtag[40], url[BUFFERSIZE];
1779 int t, i, intag, k;
1781 if (!tag) {
1782 /* The user must give us something to load up. */
1783 set_error("Bookmark tag required with this option.");
1784 return FALSE;
1787 if (strlen(tag) > MAXTAGSIZE) {
1788 set_error("Tag too long.");
1789 return FALSE;
1792 filename = g_strdup_printf(BOOKMARKS_STORAGE_FILENAME);
1793 f = fopen(filename, "r");
1794 if (f == NULL) {
1795 set_error("Couldn't open bookmarks file.");
1796 return FALSE;
1798 while (fgets(s, BUFFERSIZE-1, f)) {
1799 intag = 0;
1800 t = strlen(s) - 1;
1801 while (isspace(s[t]))
1802 t--;
1803 if (s[t] != ']') continue;
1804 while (t > 0) {
1805 if (s[t] == ']') {
1806 if (!intag)
1807 intag = t;
1808 else
1809 intag = 0;
1810 } else {
1811 if (s[t] == '[') {
1812 if (intag) {
1813 i = 0;
1814 k = t + 1;
1815 while (k < intag)
1816 foundtag[i++] = s[k++];
1817 foundtag[i] = '\0';
1818 /* foundtag now contains the tag */
1819 if (strlen(foundtag) < MAXTAGSIZE && strcmp(tag, foundtag) == 0) {
1820 i = 0;
1821 while (isspace(s[i])) i++;
1822 k = 0;
1823 while (s[i] && !isspace(s[i])) url[k++] = s[i++];
1824 url[k] = '\0';
1825 Arg x = { .i = TargetNew, .s = url };
1826 open_arg(&x);
1829 intag = 0;
1832 t--;
1835 return TRUE;
1838 void
1839 toggle_proxy(gboolean onoff) {
1840 SoupURI *proxy_uri;
1841 char *filename, *new;
1842 int len;
1844 if (onoff == FALSE) {
1845 g_object_set(session, "proxy-uri", NULL, NULL);
1846 give_feedback("Proxy deactivated");
1847 } else {
1848 filename = (char *)g_getenv("http_proxy");
1850 /* Fallthrough to checking HTTP_PROXY as well, since this can also be
1851 * defined.
1853 if (filename == NULL)
1854 filename = (char *)g_getenv("HTTP_PROXY");
1856 if (filename != NULL && 0 < (len = strlen(filename))) {
1857 if (strstr(filename, "://") == NULL) {
1858 /* prepend http:// */
1859 new = g_malloc(sizeof("http://") + len);
1860 strcpy(new, "http://");
1861 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
1862 proxy_uri = soup_uri_new(new);
1863 } else {
1864 proxy_uri = soup_uri_new(filename);
1866 g_object_set(session, "proxy-uri", proxy_uri, NULL);
1867 give_feedback("Proxy activated");
1872 void
1873 toggle_scrollbars(gboolean onoff) {
1874 if (onoff == TRUE) {
1875 adjust_h = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(viewport));
1876 adjust_v = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(viewport));
1878 else {
1879 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
1880 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
1882 gtk_widget_set_scroll_adjustments (GTK_WIDGET(webview), adjust_h, adjust_v);
1884 return;
1887 void
1888 update_url(const char *uri) {
1889 gboolean ssl = g_str_has_prefix(uri, "https://");
1890 GdkColor color;
1891 #ifdef ENABLE_HISTORY_INDICATOR
1892 char before[] = " [";
1893 char after[] = "]";
1894 gboolean back = webkit_web_view_can_go_back(webview);
1895 gboolean fwd = webkit_web_view_can_go_forward(webview);
1897 if (!back && !fwd)
1898 before[0] = after[0] = '\0';
1899 #endif
1900 gtk_label_set_markup((GtkLabel*)status_url, g_markup_printf_escaped(
1901 #ifdef ENABLE_HISTORY_INDICATOR
1902 "<span font=\"%s\">%s%s%s%s%s</span>", statusfont, uri,
1903 before, back ? "+" : "", fwd ? "-" : "", after
1904 #else
1905 "<span font=\"%s\">%s</span>", statusfont, uri
1906 #endif
1908 gdk_color_parse(ssl ? sslbgcolor : statusbgcolor, &color);
1909 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &color);
1910 gdk_color_parse(ssl ? sslcolor : statuscolor, &color);
1911 gtk_widget_modify_fg(GTK_WIDGET(status_url), GTK_STATE_NORMAL, &color);
1912 gtk_widget_modify_fg(GTK_WIDGET(status_state), GTK_STATE_NORMAL, &color);
1915 void
1916 update_state() {
1917 char *markup;
1918 int download_count = g_list_length(activeDownloads);
1919 GString *status = g_string_new("");
1921 /* construct the status line */
1923 /* count, modkey and input buffer */
1924 g_string_append_printf(status, "%.0d", count);
1925 if (current_modkey) g_string_append_c(status, current_modkey);
1926 if (inputBuffer[0]) g_string_append_printf(status, " %s", inputBuffer);
1928 /* the number of active downloads */
1929 if (activeDownloads) {
1930 g_string_append_printf(status, " %d active %s", download_count,
1931 (download_count == 1) ? "download" : "downloads");
1934 #ifdef ENABLE_WGET_PROGRESS_BAR
1935 /* the progressbar */
1937 int progress = -1;
1938 char progressbar[progressbartick + 1];
1940 if (activeDownloads) {
1941 progress = 0;
1942 GList *ptr;
1944 for (ptr = activeDownloads; ptr; ptr = g_list_next(ptr)) {
1945 progress += 100 * webkit_download_get_progress(ptr->data);
1948 progress /= download_count;
1950 } else if (webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FINISHED
1951 && webkit_web_view_get_load_status(webview) != WEBKIT_LOAD_FAILED) {
1953 progress = webkit_web_view_get_progress(webview) * 100;
1956 if (progress >= 0) {
1957 ascii_bar(progressbartick, progress * progressbartick / 100, progressbar);
1958 g_string_append_printf(status, " %c%s%c",
1959 progressborderleft, progressbar, progressborderright);
1962 #endif
1964 /* and the current scroll position */
1966 int max = gtk_adjustment_get_upper(adjust_v) - gtk_adjustment_get_page_size(adjust_v);
1967 int val = (int)(gtk_adjustment_get_value(adjust_v) / max * 100);
1969 if (max == 0)
1970 g_string_append(status, " All");
1971 else if (val == 0)
1972 g_string_append(status, " Top");
1973 else if (val == 100)
1974 g_string_append(status, " Bot");
1975 else
1976 g_string_append_printf(status, " %d%%", val);
1980 markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", statusfont, status->str);
1981 gtk_label_set_markup(GTK_LABEL(status_state), markup);
1983 g_string_free(status, TRUE);
1986 void
1987 setup_modkeys() {
1988 unsigned int i;
1989 modkeys = calloc(LENGTH(keys) + 1, sizeof(char));
1990 char *ptr = modkeys;
1992 for (i = 0; i < LENGTH(keys); i++)
1993 if (keys[i].modkey && !strchr(modkeys, keys[i].modkey))
1994 *(ptr++) = keys[i].modkey;
1995 modkeys = realloc(modkeys, &ptr[0] - &modkeys[0] + 1);
1998 void
1999 setup_gui() {
2000 scroll_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
2001 scroll_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
2002 adjust_h = gtk_range_get_adjustment(GTK_RANGE(scroll_h));
2003 adjust_v = gtk_range_get_adjustment(GTK_RANGE(scroll_v));
2004 if (embed) {
2005 window = (GtkWindow *)gtk_plug_new(embed);
2006 } else {
2007 window = (GtkWindow *)gtk_window_new(GTK_WINDOW_TOPLEVEL);
2008 gtk_window_set_wmclass(GTK_WINDOW(window), "vimprobable2", "Vimprobable2");
2010 gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
2011 box = GTK_BOX(gtk_vbox_new(FALSE, 0));
2012 inputbox = gtk_entry_new();
2013 webview = (WebKitWebView*)webkit_web_view_new();
2014 GtkBox *statusbar = GTK_BOX(gtk_hbox_new(FALSE, 0));
2015 eventbox = gtk_event_box_new();
2016 status_url = gtk_label_new(NULL);
2017 status_state = gtk_label_new(NULL);
2018 GdkColor bg;
2019 PangoFontDescription *font;
2020 GdkGeometry hints = { 1, 1 };
2021 inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview));
2023 clipboards[0] = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
2024 clipboards[1] = gtk_clipboard_get(GDK_NONE);
2025 setup_settings();
2026 gdk_color_parse(statusbgcolor, &bg);
2027 gtk_widget_modify_bg(eventbox, GTK_STATE_NORMAL, &bg);
2028 gtk_widget_set_name(GTK_WIDGET(window), "Vimprobable2");
2029 gtk_window_set_geometry_hints(window, NULL, &hints, GDK_HINT_MIN_SIZE);
2031 #ifdef DISABLE_SCROLLBAR
2032 viewport = gtk_scrolled_window_new(NULL, NULL);
2033 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(viewport), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
2034 #else
2035 /* Ensure we still see scrollbars. */
2036 GtkWidget *viewport = gtk_scrolled_window_new(adjust_h, adjust_v);
2037 #endif
2039 setup_signals();
2040 gtk_container_add(GTK_CONTAINER(viewport), GTK_WIDGET(webview));
2042 /* Ensure we set the scroll adjustments now, so that if we're not drawing
2043 * titlebars, we can still scroll.
2045 gtk_widget_set_scroll_adjustments(GTK_WIDGET(webview), adjust_h, adjust_v);
2047 font = pango_font_description_from_string(urlboxfont[0]);
2048 gtk_widget_modify_font(GTK_WIDGET(inputbox), font);
2049 pango_font_description_free(font);
2050 gtk_entry_set_inner_border(GTK_ENTRY(inputbox), NULL);
2051 gtk_misc_set_alignment(GTK_MISC(status_url), 0.0, 0.0);
2052 gtk_misc_set_alignment(GTK_MISC(status_state), 1.0, 0.0);
2053 gtk_box_pack_start(statusbar, status_url, TRUE, TRUE, 2);
2054 gtk_box_pack_start(statusbar, status_state, FALSE, FALSE, 2);
2055 gtk_container_add(GTK_CONTAINER(eventbox), GTK_WIDGET(statusbar));
2056 gtk_box_pack_start(box, viewport, TRUE, TRUE, 0);
2057 gtk_box_pack_start(box, eventbox, FALSE, FALSE, 0);
2058 gtk_entry_set_has_frame(GTK_ENTRY(inputbox), FALSE);
2059 gtk_box_pack_end(box, inputbox, FALSE, FALSE, 0);
2060 gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(box));
2061 gtk_widget_grab_focus(GTK_WIDGET(webview));
2062 gtk_widget_show_all(GTK_WIDGET(window));
2065 void
2066 setup_settings() {
2067 WebKitWebSettings *settings = (WebKitWebSettings*)webkit_web_settings_new();
2068 SoupURI *proxy_uri;
2069 char *filename, *new;
2070 int len;
2072 session = webkit_get_default_session();
2073 g_object_set((GObject*)settings, "default-font-size", DEFAULT_FONT_SIZE, NULL);
2074 g_object_set((GObject*)settings, "enable-scripts", enablePlugins, NULL);
2075 g_object_set((GObject*)settings, "enable-plugins", enablePlugins, NULL);
2076 g_object_set((GObject*)settings, "enable-java-applet", enableJava, NULL);
2077 filename = g_strdup_printf(USER_STYLESHEET);
2078 filename = g_strdup_printf("file://%s", filename);
2079 g_object_set((GObject*)settings, "user-stylesheet-uri", filename, NULL);
2080 g_object_set((GObject*)settings, "user-agent", useragent, NULL);
2081 g_object_get((GObject*)settings, "zoom-step", &zoomstep, NULL);
2082 webkit_web_view_set_settings(webview, settings);
2084 /* proxy */
2085 if (use_proxy == TRUE) {
2086 filename = (char *)g_getenv("http_proxy");
2087 if (filename != NULL && 0 < (len = strlen(filename))) {
2088 if (strstr(filename, "://") == NULL) {
2089 /* prepend http:// */
2090 new = g_malloc(sizeof("http://") + len);
2091 strcpy(new, "http://");
2092 memcpy(&new[sizeof("http://") - 1], filename, len + 1);
2093 proxy_uri = soup_uri_new(new);
2094 } else {
2095 proxy_uri = soup_uri_new(filename);
2097 g_object_set(session, "proxy-uri", proxy_uri, NULL);
2102 void
2103 setup_signals() {
2104 #ifdef ENABLE_COOKIE_SUPPORT
2105 /* Headers. */
2106 g_signal_connect_after((GObject*)session, "request-started", (GCallback)new_generic_request, NULL);
2107 #endif
2108 /* window */
2109 g_object_connect((GObject*)window,
2110 "signal::destroy", (GCallback)window_destroyed_cb, NULL,
2111 NULL);
2112 /* webview */
2113 g_object_connect((GObject*)webview,
2114 "signal::title-changed", (GCallback)webview_title_changed_cb, NULL,
2115 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, NULL,
2116 "signal::load-committed", (GCallback)webview_load_committed_cb, NULL,
2117 "signal::load-finished", (GCallback)webview_load_finished_cb, NULL,
2118 "signal::navigation-policy-decision-requested", (GCallback)webview_navigation_cb, NULL,
2119 "signal::new-window-policy-decision-requested", (GCallback)webview_new_window_cb, NULL,
2120 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, NULL,
2121 "signal::download-requested", (GCallback)webview_download_cb, NULL,
2122 "signal::key-press-event", (GCallback)webview_keypress_cb, NULL,
2123 "signal::hovering-over-link", (GCallback)webview_hoverlink_cb, NULL,
2124 "signal::console-message", (GCallback)webview_console_cb, NULL,
2125 "signal::create-web-view", (GCallback)webview_open_in_new_window_cb, NULL,
2126 "signal::event", (GCallback)notify_event_cb, NULL,
2127 NULL);
2128 /* webview adjustment */
2129 g_object_connect((GObject*)adjust_v,
2130 "signal::value-changed", (GCallback)webview_scroll_cb, NULL,
2131 NULL);
2132 /* inputbox */
2133 g_object_connect((GObject*)inputbox,
2134 "signal::activate", (GCallback)inputbox_activate_cb, NULL,
2135 "signal::key-press-event", (GCallback)inputbox_keypress_cb, NULL,
2136 "signal::key-release-event", (GCallback)inputbox_keyrelease_cb, NULL,
2137 #ifdef ENABLE_INCREMENTAL_SEARCH
2138 "signal::changed", (GCallback)inputbox_changed_cb, NULL,
2139 #endif
2140 NULL);
2141 /* inspector */
2142 g_signal_connect((GObject*)inspector,
2143 "inspect-web-view", (GCallback)inspector_inspect_web_view_cb, NULL);
2146 #ifdef ENABLE_COOKIE_SUPPORT
2147 void
2148 setup_cookies()
2150 if (file_cookie_jar)
2151 g_object_unref(file_cookie_jar);
2153 if (session_cookie_jar)
2154 g_object_unref(session_cookie_jar);
2156 session_cookie_jar = soup_cookie_jar_new();
2157 cookie_store = g_strdup_printf(COOKIES_STORAGE_FILENAME);
2159 lock = open(cookie_store, 0);
2160 flock(lock, LOCK_EX);
2162 load_all_cookies();
2164 flock(lock, LOCK_UN);
2165 close(lock);
2167 soup_session_add_feature(session, SOUP_SESSION_FEATURE(session_cookie_jar));
2168 soup_session_add_feature(session, SOUP_SESSION_FEATURE(file_cookie_jar));
2170 return;
2173 /* TA: XXX - we should be using this callback for any header-requests we
2174 * receive (hence the name "new_generic_request" -- but for now, its use
2175 * is limited to handling cookies.
2177 void
2178 new_generic_request(SoupSession *session, SoupMessage *soup_msg, gpointer unused) {
2179 SoupMessageHeaders *soup_msg_h;
2180 SoupURI *uri;
2181 const char *cookie_str;
2183 soup_msg_h = soup_msg->request_headers;
2184 soup_message_headers_remove(soup_msg_h, "Cookie");
2185 uri = soup_message_get_uri(soup_msg);
2186 if( (cookie_str = get_cookies(uri)) )
2187 soup_message_headers_append(soup_msg_h, "Cookie", cookie_str);
2189 g_signal_connect_after(G_OBJECT(soup_msg), "got-headers", G_CALLBACK(handle_cookie_request), NULL);
2191 return;
2194 const char *
2195 get_cookies(SoupURI *soup_uri) {
2196 const char *cookie_str;
2198 cookie_str = soup_cookie_jar_get_cookies(session_cookie_jar, soup_uri, TRUE);
2200 return cookie_str;
2203 void
2204 handle_cookie_request(SoupMessage *soup_msg, gpointer unused)
2206 GSList *resp_cookie = NULL;
2207 SoupCookie *cookie;
2209 for(resp_cookie = soup_cookies_from_response(soup_msg);
2210 resp_cookie;
2211 resp_cookie = g_slist_next(resp_cookie))
2213 SoupDate *soup_date;
2214 cookie = soup_cookie_copy((SoupCookie *)resp_cookie->data);
2216 if (cookie_timeout && cookie->expires == NULL) {
2217 soup_date = soup_date_new_from_time_t(time(NULL) + cookie_timeout * 10);
2218 soup_cookie_set_expires(cookie, soup_date);
2220 soup_cookie_jar_add_cookie(session_cookie_jar, cookie);
2221 update_cookie_jar(cookie);
2224 return;
2227 void
2228 update_cookie_jar(SoupCookie *new)
2230 if (!new) {
2231 /* Nothing to do. */
2232 return;
2235 /* TA: Note that this locking is merely advisory -- because there's
2236 * no linking between different vimprobable processes, the cookie jar,
2237 * when being written to here, *WILL* be truncated and overwritten
2238 * with cookie contents from the specific session saving its cookies
2239 * at that time.
2241 * This may or may not contain cookies stored in other Vimprobable
2242 * instances, although when those instances save their cookies,
2243 * they'll just replace the cookie store's contents.
2245 * The locking should probably be changed to be fcntl() based one day
2246 * -- but the caveat with that is all Vimprobable instances would then
2247 * block waiting for the cookie file to become availabe. The
2248 * advisory locking used here so far seems to work OK, but if we run
2249 * into problems in the future, we'll know where to look.
2251 * Ideally, if Vimprobable were ever to want to do this cleanly,
2252 * something like using libunique to pass messages between registered
2253 * sessions.
2255 lock = open(cookie_store, 0);
2256 flock(lock, LOCK_EX);
2258 save_all_cookies();
2259 load_all_cookies();
2261 flock(lock, LOCK_UN);
2262 close(lock);
2264 return;
2267 void
2268 save_all_cookies(void)
2270 GSList *session_cookie_list = soup_cookie_jar_all_cookies(session_cookie_jar);
2272 for (; session_cookie_list;
2273 session_cookie_list = session_cookie_list->next)
2275 soup_cookie_jar_add_cookie(file_cookie_jar, session_cookie_list->data);
2278 soup_cookies_free(session_cookie_list);
2280 return;
2283 void
2284 load_all_cookies(void)
2286 file_cookie_jar = soup_cookie_jar_text_new(cookie_store, COOKIES_STORAGE_READONLY);
2288 /* Put them back in the session store. */
2289 GSList *cookies_from_file = soup_cookie_jar_all_cookies(file_cookie_jar);
2291 for (; cookies_from_file;
2292 cookies_from_file = cookies_from_file->next)
2294 soup_cookie_jar_add_cookie(session_cookie_jar, cookies_from_file->data);
2297 soup_cookies_free(cookies_from_file);
2299 return;
2302 #endif
2304 void
2305 mop_up(void) {
2306 /* Free up any nasty globals before exiting. */
2307 #ifdef ENABLE_COOKIE_SUPPORT
2308 if (cookie_store)
2309 g_free(cookie_store);
2310 #endif
2311 return;
2315 main(int argc, char *argv[]) {
2316 static Arg a;
2317 static char url[256] = "";
2318 static gboolean ver = false;
2319 static gboolean configfile_exists = FALSE;
2320 static const char *cfile = NULL;
2321 static GOptionEntry opts[] = {
2322 { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
2323 { "embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "embedded", NULL },
2324 { "configfile", 'c', 0, G_OPTION_ARG_STRING, &cfile, "config file", NULL },
2325 { NULL }
2327 static GError *err;
2328 args = argv;
2330 /* command line argument: version */
2331 if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
2332 g_printerr("can't init gtk: %s\n", err->message);
2333 g_error_free(err);
2334 return EXIT_FAILURE;
2337 if (ver) {
2338 printf("%s\n", useragent);
2339 return EXIT_SUCCESS;
2342 if (cfile)
2343 configfile = g_strdup_printf(cfile);
2344 else
2345 configfile = g_strdup_printf(RCFILE);
2347 if (!g_thread_supported())
2348 g_thread_init(NULL);
2350 if (winid)
2351 embed = atoi(winid);
2353 setup_modkeys();
2354 make_keyslist();
2355 setup_gui();
2356 #ifdef ENABLE_COOKIE_SUPPORT
2357 setup_cookies();
2358 #endif
2360 /* Check if the specified file exists. */
2361 /* And only warn the user, if they explicitly asked for a config on the
2362 * command line.
2364 if (!(access(configfile, F_OK) == 0) && cfile) {
2365 char *feedback_str;
2367 feedback_str = g_strdup_printf("Config file '%s' doesn't exist", cfile);
2368 give_feedback(feedback_str);
2369 } else if ((access(configfile, F_OK) == 0))
2370 configfile_exists = true;
2372 /* read config file */
2373 /* But only report errors if we failed, and the file existed. */
2374 if (!read_rcfile(configfile) && configfile_exists) {
2375 a.i = Error;
2376 a.s = g_strdup_printf("Error in config file '%s'", configfile);
2377 echo(&a);
2378 g_free(configfile);
2381 /* command line argument: URL */
2382 if (argc > 1) {
2383 strncpy(url, argv[argc - 1], 255);
2384 } else {
2385 strncpy(url, startpage, 255);
2388 a.i = TargetCurrent;
2389 a.s = url;
2390 open_arg(&a);
2391 gtk_main();
2393 mop_up();
2395 return EXIT_SUCCESS;