Fixed some warnings.
[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 cb_toggle_status() {
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    time_t rawtime;
178    struct tm * timeinfo;
179    char date [80];
180    time ( &rawtime );
181    timeinfo = localtime ( &rawtime );
182    strftime (date, 80, "%Y-%m-%d %H:%M:%S", timeinfo);
183    GString* args = g_string_new ("");
184    g_string_printf (args, "'%s' '%s' '%s'", uri, "TODO:page title here", date);
185    run_command(history_handler, args->str);
186 }
187
188 /* -- command to callback/function map for things we cannot attach to any signals */
189 // TODO: reload, home, quit
190 static Command commands[] =
191 {
192     { "back",     &go_back_cb,                    NULL },
193     { "forward",  &go_forward_cb,                 NULL },
194     { "refresh",  &webkit_web_view_reload,        NULL }, //Buggy
195     { "stop",     &webkit_web_view_stop_loading,  NULL },
196     { "zoom_in",  &webkit_web_view_zoom_in,       NULL }, //Can crash (when max zoom reached?).
197     { "zoom_out", &webkit_web_view_zoom_out,      NULL },
198     { "uri",      NULL, &webkit_web_view_load_uri      },
199     { "toggle_status", &cb_toggle_status, NULL}
200 //{ "get uri",  &webkit_web_view_get_uri},
201 };
202
203 /* -- CORE FUNCTIONS -- */
204
205 static bool
206 file_exists (const char * filename)
207 {
208     FILE *file = fopen (filename, "r");
209     if (file) {
210         fclose (file);
211         return true;
212     }
213     return false;
214 }
215
216 // make sure to put '' around args, so that if there is whitespace we can still keep arguments together.
217 static gboolean
218 run_command(const char *command, const char *args) {
219    //command <uzbl conf> <uzbl pid> <uzbl win id> <uzbl fifo file> [args]
220     GString* to_execute = g_string_new ("");
221     gboolean result;
222     g_string_printf (to_execute, "%s '%s' '%i' '%i' '%s' %s", command, config_file, (int) getpid() , (int) xwin, "/tmp/uzbl_25165827", args);
223     result = system(to_execute->str);
224     printf("Called %s.  Result: %s\n", to_execute->str, (result ? "FALSE" : "TRUE" ));
225     return result;
226 }
227
228 static void
229 parse_command(const char *command) {
230     int i;
231     Command *c = NULL;
232     char * command_name  = strtok (command, " ");
233     char * command_param = strtok (NULL,  " ,"); //dunno how this works, but it seems to work
234
235     Command *c_tmp = NULL;
236     for (i = 0; i < LENGTH (commands); i++) {
237         c_tmp = &commands[i];
238         if (strncmp (command_name, c_tmp->command, strlen (c_tmp->command)) == 0) {
239             c = c_tmp;
240         }
241     }
242     if (c != NULL) {
243         if (c->func_2_params != NULL) {
244             if (command_param != NULL) {
245                 printf ("command executing: \"%s %s\"\n", command_name, command_param);
246                 c->func_2_params (web_view, command_param);
247             } else {
248                 if (c->func_1_param != NULL) {
249                     printf ("command executing: \"%s\"\n", command_name);
250                     c->func_1_param (web_view);
251                 } else {
252                     fprintf (stderr, "command needs a parameter. \"%s\" is not complete\n", command_name);
253                 }
254             }
255         } else if (c->func_1_param != NULL) {
256             printf ("command executing: \"%s\"\n", command_name);
257             c->func_1_param (web_view);
258         }
259     } else {
260         fprintf (stderr, "command \"%s\" not understood. ignoring.\n", command);
261     }
262 }
263  
264 static void
265 *control_fifo() {
266     if (fifodir) {
267         sprintf (fifopath, "%s/uzbl_%d", fifodir, (int) xwin);
268     } else {
269         sprintf (fifopath, "/tmp/uzbl_%d", (int) xwin);
270     }
271  
272     if (mkfifo (fifopath, 0666) == -1) {
273         printf ("Possible error creating fifo\n");
274     }
275  
276     printf ("Control fifo opened in %s\n", fifopath);
277  
278     while (true) {
279         FILE *fifo = fopen (fifopath, "r");
280         if (!fifo) {
281             printf ("Could not open %s for reading\n", fifopath);
282             return NULL;
283         }
284         
285         char buffer[256];
286         memset (buffer, 0, sizeof (buffer));
287         while (!feof (fifo) && fgets (buffer, sizeof (buffer), fifo)) {
288             if (strcmp (buffer, "\n")) {
289                 buffer[strlen (buffer) - 1] = '\0'; // Remove newline
290                 parse_command (buffer);
291             }
292         }
293     }
294     
295     return NULL;
296
297  
298 static void
299 setup_threading () {
300     pthread_t control_thread;
301     pthread_create(&control_thread, NULL, control_fifo, NULL);
302 }
303
304 static void
305 update_title (GtkWindow* window) {
306     GString* string_long = g_string_new ("");
307     GString* string_short = g_string_new ("");
308     if (!always_insert_mode)
309         g_string_append (string_long, (insert_mode ? "[I] " : "[C] "));
310     g_string_append (string_long, main_title);
311     g_string_append (string_short, main_title);
312     g_string_append (string_long, " - Uzbl browser");
313     g_string_append (string_short, " - Uzbl browser");
314     if (load_progress < 100)
315         g_string_append_printf (string_long, " (%d%%)", load_progress);
316
317     if (selected_url[0]!=0) {
318         g_string_append_printf (string_long, " -> (%s)", selected_url);
319     }
320
321     gchar* title_long = g_string_free (string_long, FALSE);
322     gchar* title_short = g_string_free (string_short, FALSE);
323
324     if (show_status) {
325         gtk_window_set_title (window, title_short);
326         gtk_label_set_text(mainbar_label, title_long);
327     } else {
328         gtk_window_set_title (window, title_long);
329     }
330
331     g_free (title_long);
332     g_free (title_short);
333 }
334  
335 static gboolean
336 key_press_cb (WebKitWebView* page, GdkEventKey* event)
337 {
338     int i;
339     gboolean result=FALSE; //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
340     if (event->type != GDK_KEY_PRESS) 
341         return result;
342
343     //TURN OFF INSERT MODE
344     if (insert_mode && (event->keyval == GDK_Escape)) {
345         insert_mode = FALSE;
346         update_title (GTK_WINDOW (main_window));
347         return TRUE;
348     }
349
350     //TURN ON INSERT MODE
351     if (!insert_mode && (event->string[0] == 'i')) {
352         insert_mode = TRUE;
353         update_title (GTK_WINDOW (main_window));
354         return TRUE;
355     }
356
357     //INTERNAL KEYS
358     if (always_insert_mode || !insert_mode) {
359         for (i = 0; i < num_internal_bindings; i++) {
360             if (event->string[0] == internal_bindings[i].binding[0]) {
361                 parse_command (internal_bindings[i].action);
362                 result = TRUE;
363             }   
364         }
365     }
366     if (!result)
367         result = (insert_mode ? FALSE : TRUE);      
368
369     return result;
370 }
371
372 static GtkWidget*
373 create_browser () {
374     GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
375     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
376
377     web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
378     gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (web_view));
379
380     g_signal_connect (G_OBJECT (web_view), "title-changed", G_CALLBACK (title_change_cb), web_view);
381     g_signal_connect (G_OBJECT (web_view), "load-progress-changed", G_CALLBACK (progress_change_cb), web_view);
382     g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (load_commit_cb), web_view);
383     g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (log_history_cb), web_view);
384     g_signal_connect (G_OBJECT (web_view), "hovering-over-link", G_CALLBACK (link_hover_cb), web_view);
385     g_signal_connect (G_OBJECT (web_view), "key-press-event", G_CALLBACK (key_press_cb), web_view);
386
387     return scrolled_window;
388 }
389
390 static GtkWidget*
391 create_mainbar () {
392     mainbar = gtk_hbox_new (FALSE, 0);
393     mainbar_label = gtk_label_new ("");  
394     gtk_misc_set_alignment (mainbar_label, 0, 0);
395     gtk_misc_set_padding (mainbar_label, 2, 2);
396     gtk_box_pack_start (GTK_BOX (mainbar), mainbar_label, TRUE, TRUE, 0);
397     return mainbar;
398 }
399
400 static
401 GtkWidget* create_window () {
402     GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
403     gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
404     gtk_widget_set_name (window, "Uzbl browser");
405     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy_cb), NULL);
406
407     return window;
408 }
409
410 static void
411 add_binding (char *binding, char *action, bool internal) {
412     Binding bind = {binding, action};
413     if (internal) {
414         internal_bindings[num_internal_bindings] = bind;
415         num_internal_bindings ++;
416     } else {
417         external_bindings[num_external_bindings] = bind;
418         num_external_bindings ++;
419     }
420 }
421
422 static void
423 settings_init () {
424     GKeyFile* config;
425     gboolean res  = FALSE;
426     gchar** keysi = NULL;
427     gchar** keyse = NULL;
428
429     if (! config_file) {
430         const char* xdg = getenv ("XDG_CONFIG_HOME");
431         char conf[256];
432         if (xdg) {
433             printf("XDG_CONFIG_DIR: %s\n", xdg);
434             strcpy (conf, xdg);
435             strcat (conf, "/uzbl");
436             if (file_exists (conf)) {
437                 printf ("Config file %s found.\n", conf);
438                 config_file = &conf;
439             }
440         }
441     }
442
443     if (config_file) {
444         config = g_key_file_new ();
445         res = g_key_file_load_from_file (config, config_file, G_KEY_FILE_NONE, NULL);
446           if(res) {
447             printf ("Config %s loaded\n", config_file);
448           } else {
449             fprintf (stderr, "Config %s loading failed\n", config_file);
450         }
451     } else {
452         printf ("No configuration.\n");
453     }
454
455     if (res) {
456         history_handler    = g_key_file_get_value   (config, "behavior", "history_handler", NULL);
457         download_handler   = g_key_file_get_value   (config, "behavior", "download_handler", NULL);
458         always_insert_mode = g_key_file_get_boolean (config, "behavior", "always_insert_mode", NULL);
459         show_status        = g_key_file_get_boolean (config, "behavior", "show_status", NULL);
460         modkey             = g_key_file_get_value   (config, "behavior", "modkey", NULL);
461         keysi              = g_key_file_get_keys    (config, "bindings_internal", NULL, NULL);
462         keyse              = g_key_file_get_keys    (config, "bindings_external", NULL, NULL);
463         if (! fifodir)
464             fifodir        = g_key_file_get_value   (config, "behavior", "fifodir", NULL);
465     }
466         
467     if (history_handler) {
468         printf ("History handler: %s\n", history_handler);
469     } else {
470         printf ("History handler disabled\n");
471     }
472
473     if (download_handler) {
474         printf ("Download manager: %s\n", download_handler);
475     } else {
476         printf ("Download manager disabled\n");
477     }
478
479     if (fifodir) {
480         printf ("Fifo directory: %s\n", fifodir);
481     } else {
482         printf ("Fifo directory: /tmp\n");
483     }
484
485     printf ("Always insert mode: %s\n", (always_insert_mode ? "TRUE" : "FALSE"));
486
487     printf ("Show status: %s\n", (show_status ? "TRUE" : "FALSE"));
488
489     status_top = g_key_file_get_boolean (config, "behavior", "status_top", NULL);
490     printf ("Status top: %s\n", (status_top ? "TRUE" : "FALSE"));
491
492     if (modkey) {
493         printf ("Modkey: %s\n", modkey);
494     } else {
495         printf ("Modkey disabled\n");
496     }
497
498     if (keysi) {
499               int i = 0;
500         for (i = 0; keysi[i]; i++) {
501             gchar *binding = g_key_file_get_string (config, "bindings_internal", keysi[i], NULL);
502             printf ("Action: %s, Binding: %s (internal)\n", g_strdup (keysi[i]), binding);
503             add_binding (binding, g_strdup (keysi[i]), true);
504         }
505     }
506     if (keyse) {
507               int i = 0;
508         for (i = 0; keyse[i]; i++) {
509             gchar *binding = g_key_file_get_string (config, "bindings_external", keyse[i], NULL);
510             printf ("Action: %s, Binding: %s (external)\n", g_strdup (keyse[i]), binding);
511             add_binding (binding, g_strdup (keyse[i]), false);
512         }
513     }
514 }
515
516 int
517 main (int argc, char* argv[]) {
518     gtk_init (&argc, &argv);
519     if (!g_thread_supported ())
520         g_thread_init (NULL);
521
522     GError *error = NULL;
523     GOptionContext* context = g_option_context_new ("- some stuff here maybe someday");
524     g_option_context_add_main_entries (context, entries, NULL);
525     g_option_context_add_group (context, gtk_get_option_group (TRUE));
526     g_option_context_parse (context, &argc, &argv, &error);
527
528     settings_init ();
529     if (always_insert_mode)
530         insert_mode = TRUE;
531
532     GtkWidget* vbox = gtk_vbox_new (FALSE, 0);
533     if (status_top)
534         gtk_box_pack_start (GTK_BOX (vbox), create_mainbar (), FALSE, TRUE, 0);
535     gtk_box_pack_start (GTK_BOX (vbox), create_browser (), TRUE, TRUE, 0);
536     if (!status_top)
537         gtk_box_pack_start (GTK_BOX (vbox), create_mainbar (), FALSE, TRUE, 0);
538
539     main_window = create_window ();
540     gtk_container_add (GTK_CONTAINER (main_window), vbox);
541
542     webkit_web_view_load_uri (web_view, uri);
543
544     gtk_widget_grab_focus (GTK_WIDGET (web_view));
545     gtk_widget_show_all (main_window);
546     xwin = GDK_WINDOW_XID (GTK_WIDGET (main_window)->window);
547     printf("window_id %i\n",(int) xwin);
548     printf("pid %i\n", getpid ());
549
550     if (!show_status)
551         gtk_widget_hide(mainbar);
552
553     setup_threading ();
554
555     gtk_main ();
556
557     unlink (fifopath);
558     return 0;
559 }