Merge branch 'dieter/experimental' into experimental
[uzbl-mobile] / uzbl.c
1 // Original code taken from the example webkit-gtk+ application. see notice below.
2 // Modified code is licensed under the GPL 3.  See LICENSE file.
3
4
5 /*
6  * Copyright (C) 2006, 2007 Apple Inc.
7  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31
32 #define LENGTH(x)               (sizeof x / sizeof x[0])
33 #define GDK_Escape 0xff1b
34
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37 #include <webkit/webkit.h>
38 #include <pthread.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45
46 /* housekeeping / internal variables */
47 static GtkWidget* main_window;
48 static GtkWidget* mainbar;
49 static GtkWidget* mainbar_label;
50 static WebKitWebView* web_view;
51 static gchar* main_title;
52 static gchar selected_url[500];
53 static gint load_progress;
54 static guint status_context_id;
55 static Window xwin = 0;
56 static char fifopath[64];
57
58 /* state variables (initial values coming from command line arguments but may be changed later) */
59 static gchar*   uri         = NULL;
60 static gchar*   config_file = NULL;
61 static gboolean verbose     = FALSE;
62
63 /* settings from config: group behaviour */
64 static gchar*   history_handler    = NULL;
65 static gchar*   fifodir            = NULL;
66 static gchar*   download_handler   = NULL;
67 static gboolean always_insert_mode = FALSE;
68 static gboolean show_status        = FALSE;
69 static gboolean insert_mode        = FALSE;
70 static gboolean status_top         = FALSE;
71 static gchar*   modkey             = NULL;
72
73
74 typedef struct
75 {
76     const char *binding;
77     const char *action;
78 } Binding;
79
80 /* settings from config: group bindings_internal */
81 static Binding internal_bindings[256];
82 static int     num_internal_bindings = 0;
83
84 /* settings from config: group bindings_external */
85 static Binding external_bindings[256];
86 static int     num_external_bindings = 0;
87
88 /* commandline arguments (set initial values for the state variables) */
89 static GOptionEntry entries[] =
90 {
91     { "uri",     'u', 0, G_OPTION_ARG_STRING, &uri,         "Uri to load", NULL },
92     { "verbose", 'v', 0, G_OPTION_ARG_NONE,   &verbose,     "Be verbose",  NULL },
93     { "config",  'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file", NULL },
94     { NULL }
95 };
96
97 /* for internal list of commands */
98 typedef struct
99 {
100     const char *command;
101     void (*func_1_param)(WebKitWebView*);
102     void (*func_2_params)(WebKitWebView*, char *);
103 } Command;
104
105
106 static void
107 update_title (GtkWindow* window);
108
109 static gboolean
110 run_command(const char *command, const char *args);
111
112
113 /* --- CALLBACKS --- */
114 static void
115 go_back_cb (GtkWidget* widget, gpointer data) {
116     webkit_web_view_go_back (web_view);
117 }
118
119 static void
120 go_forward_cb (GtkWidget* widget, gpointer data) {
121     webkit_web_view_go_forward (web_view);
122 }
123
124 static void
125 toggle_status_cb() {
126     if (show_status) {
127         gtk_widget_hide(mainbar);
128     } else {
129         gtk_widget_show(mainbar);
130     }
131     show_status = !show_status;
132     update_title (GTK_WINDOW (main_window));
133 }
134
135 static void
136 link_hover_cb (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) {
137     /* underflow is allowed */
138     //gtk_statusbar_pop (main_statusbar, status_context_id);
139     //if (link)
140     //    gtk_statusbar_push (main_statusbar, status_context_id, link);
141     //TODO implementation roadmap pending..
142     
143     //ADD HOVER URL TO WINDOW TITLE
144     selected_url[0] = '\0';
145     if (link) {
146         strcpy (selected_url, link);
147     }
148     update_title (GTK_WINDOW (main_window));
149 }
150
151 static void
152 title_change_cb (WebKitWebView* web_view, WebKitWebFrame* web_frame, const gchar* title, gpointer data) {
153     if (main_title)
154         g_free (main_title);
155     main_title = g_strdup (title);
156     update_title (GTK_WINDOW (main_window));
157 }
158
159 static void
160 progress_change_cb (WebKitWebView* page, gint progress, gpointer data) {
161     load_progress = progress;
162     update_title (GTK_WINDOW (main_window));
163 }
164
165 static void
166 load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data) {
167     strcpy (uri, webkit_web_frame_get_uri (frame));
168 }
169
170 static void
171 destroy_cb (GtkWidget* widget, gpointer data) {
172     gtk_main_quit ();
173 }
174
175 static void
176 log_history_cb () {
177    if (history_handler) {
178        time_t rawtime;
179        struct tm * timeinfo;
180        char date [80];
181        time ( &rawtime );
182        timeinfo = localtime ( &rawtime );
183        strftime (date, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
184        GString* args = g_string_new ("");
185        g_string_printf (args, "'%s' '%s' '%s'", uri, "TODO:page title here", date);
186        run_command(history_handler, args->str);
187    }
188 }
189
190 /* -- command to callback/function map for things we cannot attach to any signals */
191 // TODO: reload, home, quit
192 static Command commands[] =
193 {
194     { "back",          &go_back_cb,                    NULL },
195     { "forward",       &go_forward_cb,                 NULL },
196     { "refresh",       &webkit_web_view_reload,        NULL }, //Buggy
197     { "stop",          &webkit_web_view_stop_loading,  NULL },
198     { "zoom_in",       &webkit_web_view_zoom_in,       NULL }, //Can crash (when max zoom reached?).
199     { "zoom_out",      &webkit_web_view_zoom_out,      NULL },
200     { "uri",           NULL, &webkit_web_view_load_uri      },
201     { "toggle_status", &toggle_status_cb,              NULL }
202 //{ "get uri",  &webkit_web_view_get_uri},
203 };
204
205 /* -- CORE FUNCTIONS -- */
206
207 static bool
208 file_exists (const char * filename)
209 {
210     FILE *file = fopen (filename, "r");
211     if (file) {
212         fclose (file);
213         return true;
214     }
215     return false;
216 }
217
218 // make sure to put '' around args, so that if there is whitespace we can still keep arguments together.
219 static gboolean
220 run_command(const char *command, const char *args) {
221    //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> [args]
222     GString* to_execute = g_string_new ("");
223     gboolean result;
224     g_string_printf (to_execute, "%s '%s' '%i' '%i' '%s' %s", command, config_file, (int) getpid() , (int) xwin, "/tmp/uzbl_25165827", args);
225     result = system(to_execute->str);
226     printf("Called %s.  Result: %s\n", to_execute->str, (result ? "FALSE" : "TRUE" ));
227     return result;
228 }
229
230 static void
231 parse_command(const char *command) {
232     int i;
233     Command *c = NULL;
234     char * command_name  = strtok (command, " ");
235     char * command_param = strtok (NULL,  " ,"); //dunno how this works, but it seems to work
236
237     Command *c_tmp = NULL;
238     for (i = 0; i < LENGTH (commands); i++) {
239         c_tmp = &commands[i];
240         if (strncmp (command_name, c_tmp->command, strlen (c_tmp->command)) == 0) {
241             c = c_tmp;
242         }
243     }
244     if (c != NULL) {
245         if (c->func_2_params != NULL) {
246             if (command_param != NULL) {
247                 printf ("command executing: \"%s %s\"\n", command_name, command_param);
248                 c->func_2_params (web_view, command_param);
249             } else {
250                 if (c->func_1_param != NULL) {
251                     printf ("command executing: \"%s\"\n", command_name);
252                     c->func_1_param (web_view);
253                 } else {
254                     fprintf (stderr, "command needs a parameter. \"%s\" is not complete\n", command_name);
255                 }
256             }
257         } else if (c->func_1_param != NULL) {
258             printf ("command executing: \"%s\"\n", command_name);
259             c->func_1_param (web_view);
260         }
261     } else {
262         fprintf (stderr, "command \"%s\" not understood. ignoring.\n", command);
263     }
264 }
265  
266 static void
267 *control_fifo() {
268     if (fifodir) {
269         sprintf (fifopath, "%s/uzbl_%d", fifodir, (int) xwin);
270     } else {
271         sprintf (fifopath, "/tmp/uzbl_%d", (int) xwin);
272     }
273  
274     if (mkfifo (fifopath, 0666) == -1) {
275         printf ("Possible error creating fifo\n");
276     }
277  
278     printf ("Control fifo opened in %s\n", fifopath);
279  
280     while (true) {
281         FILE *fifo = fopen (fifopath, "r");
282         if (!fifo) {
283             printf ("Could not open %s for reading\n", fifopath);
284             return NULL;
285         }
286         
287         char buffer[256];
288         memset (buffer, 0, sizeof (buffer));
289         while (!feof (fifo) && fgets (buffer, sizeof (buffer), fifo)) {
290             if (strcmp (buffer, "\n")) {
291                 buffer[strlen (buffer) - 1] = '\0'; // Remove newline
292                 parse_command (buffer);
293             }
294         }
295     }
296     
297     return NULL;
298
299  
300 static void
301 setup_threading () {
302     pthread_t control_thread;
303     pthread_create(&control_thread, NULL, control_fifo, NULL);
304 }
305
306 static void
307 update_title (GtkWindow* window) {
308     GString* string_long = g_string_new ("");
309     GString* string_short = g_string_new ("");
310     if (!always_insert_mode)
311         g_string_append (string_long, (insert_mode ? "[I] " : "[C] "));
312     g_string_append (string_long, main_title);
313     g_string_append (string_short, main_title);
314     g_string_append (string_long, " - Uzbl browser");
315     g_string_append (string_short, " - Uzbl browser");
316     if (load_progress < 100)
317         g_string_append_printf (string_long, " (%d%%)", load_progress);
318
319     if (selected_url[0]!=0) {
320         g_string_append_printf (string_long, " -> (%s)", selected_url);
321     }
322
323     gchar* title_long = g_string_free (string_long, FALSE);
324     gchar* title_short = g_string_free (string_short, FALSE);
325
326     if (show_status) {
327         gtk_window_set_title (window, title_short);
328         gtk_label_set_text(mainbar_label, title_long);
329     } else {
330         gtk_window_set_title (window, title_long);
331     }
332
333     g_free (title_long);
334     g_free (title_short);
335 }
336  
337 static gboolean
338 key_press_cb (WebKitWebView* page, GdkEventKey* event)
339 {
340     int i;
341     gboolean result=FALSE; //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
342     if (event->type != GDK_KEY_PRESS) 
343         return result;
344
345     //TURN OFF INSERT MODE
346     if (insert_mode && (event->keyval == GDK_Escape)) {
347         insert_mode = FALSE;
348         update_title (GTK_WINDOW (main_window));
349         return TRUE;
350     }
351
352     //TURN ON INSERT MODE
353     if (!insert_mode && (event->string[0] == 'i')) {
354         insert_mode = TRUE;
355         update_title (GTK_WINDOW (main_window));
356         return TRUE;
357     }
358
359     //INTERNAL KEYS
360     if (always_insert_mode || !insert_mode) {
361         for (i = 0; i < num_internal_bindings; i++) {
362             if (event->string[0] == internal_bindings[i].binding[0]) {
363                 parse_command (internal_bindings[i].action);
364                 result = TRUE;
365             }   
366         }
367     }
368     if (!result)
369         result = (insert_mode ? FALSE : TRUE);      
370
371     return result;
372 }
373
374 static GtkWidget*
375 create_browser () {
376     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
377     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_NEVER); //todo: some sort of display of position/total length. like what emacs does
378
379     web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
380     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (web_view));
381
382     g_signal_connect (G_OBJECT (web_view), "title-changed", G_CALLBACK (title_change_cb), web_view);
383     g_signal_connect (G_OBJECT (web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), web_view);
384     g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (load_commit_cb), web_view);
385     g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (log_history_cb), web_view);
386     g_signal_connect (G_OBJECT (web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), web_view);
387     g_signal_connect (G_OBJECT (web_view), "key-press-event", G_CALLBACK (key_press_cb), web_view);
388
389     return scrolled_window;
390 }
391
392 static GtkWidget*
393 create_mainbar () {
394     mainbar = gtk_hbox_new (FALSE, 0);
395     mainbar_label = gtk_label_new ("");  
396     gtk_misc_set_alignment (mainbar_label, 0, 0);
397     gtk_misc_set_padding (mainbar_label, 2, 2);
398     gtk_box_pack_start (GTK_BOX (mainbar), mainbar_label, TRUE, TRUE, 0);
399     return mainbar;
400 }
401
402 static
403 GtkWidget* create_window () {
404     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
405     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
406     gtk_widget_set_name (window, "Uzbl browser");
407     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
408
409     return window;
410 }
411
412 static void
413 add_binding (char *binding, char *action, bool internal) {
414     Binding bind = {binding, action};
415     if (internal) {
416         internal_bindings[num_internal_bindings] = bind;
417         num_internal_bindings ++;
418     } else {
419         external_bindings[num_external_bindings] = bind;
420         num_external_bindings ++;
421     }
422 }
423
424 static void
425 settings_init () {
426     GKeyFile* config;
427     gboolean res  = FALSE;
428     gchar** keysi = NULL;
429     gchar** keyse = NULL;
430
431     if (! config_file) {
432         const char* xdg = getenv ("XDG_CONFIG_HOME");
433         char conf[256];
434         if (xdg) {
435             printf("XDG_CONFIG_DIR: %s\n", xdg);
436             strcpy (conf, xdg);
437             strcat (conf, "/uzbl");
438             if (file_exists (conf)) {
439                 printf ("Config file %s found.\n", conf);
440                 config_file = &conf;
441             }
442         }
443     }
444
445     if (config_file) {
446         config = g_key_file_new ();
447         res = g_key_file_load_from_file (config, config_file, G_KEY_FILE_NONE, NULL);
448           if(res) {
449             printf ("Config %s loaded\n", config_file);
450           } else {
451             fprintf (stderr, "Config %s loading failed\n", config_file);
452         }
453     } else {
454         printf ("No configuration.\n");
455     }
456
457     if (res) {
458         history_handler    = g_key_file_get_value   (config, "behavior", "history_handler", NULL);
459         download_handler   = g_key_file_get_value   (config, "behavior", "download_handler", NULL);
460         always_insert_mode = g_key_file_get_boolean (config, "behavior", "always_insert_mode", NULL);
461         show_status        = g_key_file_get_boolean (config, "behavior", "show_status", NULL);
462         modkey             = g_key_file_get_value   (config, "behavior", "modkey", NULL);
463         keysi              = g_key_file_get_keys    (config, "bindings_internal", NULL, NULL);
464         keyse              = g_key_file_get_keys    (config, "bindings_external", NULL, NULL);
465         status_top         = g_key_file_get_boolean (config, "behavior", "status_top", NULL);
466         if (! fifodir)
467             fifodir        = g_key_file_get_value   (config, "behavior", "fifodir", NULL);
468     }
469         
470     if (history_handler) {
471         printf ("History handler: %s\n", history_handler);
472     } else {
473         printf ("History handler disabled\n");
474     }
475
476     if (download_handler) {
477         printf ("Download manager: %s\n", download_handler);
478     } else {
479         printf ("Download manager disabled\n");
480     }
481
482     if (fifodir) {
483         printf ("Fifo directory: %s\n", fifodir);
484     } else {
485         printf ("Fifo directory: /tmp\n");
486     }
487
488     printf ("Always insert mode: %s\n", (always_insert_mode ? "TRUE" : "FALSE"));
489
490     printf ("Show status: %s\n", (show_status ? "TRUE" : "FALSE"));
491
492     printf ("Status top: %s\n", (status_top ? "TRUE" : "FALSE"));
493
494     if (modkey) {
495         printf ("Modkey: %s\n", modkey);
496     } else {
497         printf ("Modkey disabled\n");
498     }
499
500     if (keysi) {
501               int i = 0;
502         for (i = 0; keysi[i]; i++) {
503             gchar *binding = g_key_file_get_string (config, "bindings_internal", keysi[i], NULL);
504             printf ("Action: %s, Binding: %s (internal)\n", g_strdup (keysi[i]), binding);
505             add_binding (binding, g_strdup (keysi[i]), true);
506         }
507     }
508     if (keyse) {
509               int i = 0;
510         for (i = 0; keyse[i]; i++) {
511             gchar *binding = g_key_file_get_string (config, "bindings_external", keyse[i], NULL);
512             printf ("Action: %s, Binding: %s (external)\n", g_strdup (keyse[i]), binding);
513             add_binding (binding, g_strdup (keyse[i]), false);
514         }
515     }
516 }
517
518 int
519 main (int argc, char* argv[]) {
520     gtk_init (&argc, &argv);
521     if (!g_thread_supported ())
522         g_thread_init (NULL);
523
524     GError *error = NULL;
525     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
526     g_option_context_add_main_entries (context, entries, NULL);
527     g_option_context_add_group (context, gtk_get_option_group (TRUE));
528     g_option_context_parse (context, &argc, &argv, &error);
529
530     settings_init ();
531     if (always_insert_mode)
532         insert_mode = TRUE;
533
534     GtkWidget* vbox = gtk_vbox_new (FALSE, 0);
535     if (status_top)
536         gtk_box_pack_start (GTK_BOX (vbox), create_mainbar (), FALSE, TRUE, 0);
537     gtk_box_pack_start (GTK_BOX (vbox), create_browser (), TRUE, TRUE, 0);
538     if (!status_top)
539         gtk_box_pack_start (GTK_BOX (vbox), create_mainbar (), FALSE, TRUE, 0);
540
541     main_window = create_window ();
542     gtk_container_add (GTK_CONTAINER (main_window), vbox);
543
544     webkit_web_view_load_uri (web_view, uri);
545
546     gtk_widget_grab_focus (GTK_WIDGET (web_view));
547     gtk_widget_show_all (main_window);
548     xwin = GDK_WINDOW_XID (GTK_WIDGET (main_window)->window);
549     printf("window_id %i\n",(int) xwin);
550     printf("pid %i\n", getpid ());
551
552     if (!show_status)
553         gtk_widget_hide(mainbar);
554
555     setup_threading ();
556
557     gtk_main ();
558
559     unlink (fifopath);
560     return 0;
561 }