Added modeline, changed commands to 1/2 characters, command/insert modes.
[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 #include <gtk/gtk.h>
32 #include <gdk/gdk.h>
33 #include <gdk/gdkx.h>
34 #include <gdk/gdkkeys.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <webkit/webkit.h>
37
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 static GtkWidget*     main_window;
47 static GtkWidget*     modeline;
48 static WebKitWebView* web_view;
49
50 static gchar* history_file;
51 static gchar* home_page;
52 static gchar* uri       = NULL;
53 static gchar* fifodir   = NULL;
54 static gint   mechmode  = 0;
55 static char   fifopath[64];
56 static bool   modevis = FALSE;
57
58 static GOptionEntry entries[] =
59 {
60   { "uri",      'u', 0, G_OPTION_ARG_STRING, &uri,      "Uri to load",                                   NULL },
61   { "fifo-dir", 'd', 0, G_OPTION_ARG_STRING, &fifodir,  "Directory to place FIFOs",                      NULL },
62   { "mechmode", 'm', 0, G_OPTION_ARG_INT,    &mechmode, "Enable output suitable for machine processing", NULL },
63   { NULL }
64 };
65
66 struct command
67 {
68   char command[256];
69   void (*func)(WebKitWebView*);
70 };
71
72 static struct command commands[256];
73 static int            numcmds = 0;
74
75 static void parse_command(char*);
76
77 static bool parse_modeline (GtkWidget* mode, GdkEventKey* event)
78 {
79   if ((event->type==GDK_KEY_PRESS) && (event->keyval==GDK_Return))
80     parse_command (gtk_entry_get_text (modeline));
81
82   return false;
83 }
84
85 static void log_history_cb (WebKitWebView* page, WebKitWebFrame* frame, gpointer data)
86 {
87   strncpy (uri, webkit_web_frame_get_uri (frame), strlen (webkit_web_frame_get_uri (frame)));
88   
89   FILE * output_file = fopen (history_file, "a");
90   if (output_file == NULL)
91     {
92       fprintf (stderr, "Cannot open %s for logging\n", history_file);
93     }
94   else
95     {
96       time_t rawtime;
97       struct tm * timeinfo;
98       char buffer [80];
99       time (&rawtime);
100       timeinfo = localtime (&rawtime);
101       strftime (buffer,80,"%Y-%m-%d %H:%M:%S",timeinfo);
102       
103       fprintf (output_file, "%s %s\n",buffer, uri);
104       fclose (output_file);
105     }
106 }
107
108 static void toggle_command_mode ()
109 {
110   if (modevis)
111     {
112       gtk_widget_hide (modeline);
113       gtk_widget_grab_default (modeline);
114     }
115   else
116     {
117       gtk_widget_show (modeline);
118       gtk_widget_grab_focus (modeline);
119     }
120   modevis = ! modevis;
121 }
122
123 static gboolean key_press_cb (WebKitWebView* page, GdkEventKey* event)
124 {
125   gboolean result=FALSE; //TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
126   if ((event->type==GDK_KEY_PRESS) && (event->keyval==GDK_Escape))
127     {
128       toggle_command_mode ();
129       result=TRUE;
130     }
131  
132   return(result);
133 }
134
135 static GtkWidget* create_browser ()
136 {
137   GtkWidget* scrolled_window = gtk_scrolled_window_new (NULL, NULL);
138   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
139
140   web_view = WEBKIT_WEB_VIEW (webkit_web_view_new ());
141   gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (web_view));
142
143   g_signal_connect (G_OBJECT (web_view), "load-committed", G_CALLBACK (log_history_cb), web_view);
144
145   return scrolled_window;
146 }
147
148 static GtkWidget* create_window ()
149 {
150   GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
151   gtk_window_set_default_size (GTK_WINDOW (window), 800, 600);
152   gtk_widget_set_name (window, "Uzbl Browser");
153   g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
154   g_signal_connect (G_OBJECT (window), "key-press-event", G_CALLBACK(key_press_cb), NULL);
155
156   return window;
157 }
158
159 static GtkWidget* create_modeline ()
160 {
161   GtkWidget* modeline = gtk_entry_new ();
162   g_signal_connect (G_OBJECT (modeline), "key-press-event", G_CALLBACK(parse_modeline), modeline);
163
164   return modeline;
165 }
166
167 static void parse_command(char *command)
168 {
169   int  i    = 0;
170   bool done = false;
171   char* strtimes = NULL;
172
173   void (*func)(WebKitWebView*);
174   int times = 1;
175
176   for (i = 0; i < numcmds && ! done; i++)
177     {
178       if (!strncmp (command, commands[i].command, strlen (commands[i].command)))
179         {
180           func = commands[i].func;
181           done = true;
182
183           if (strlen (command) > strlen (commands[i].command))
184             {
185               strtimes = (char *) command + strlen (commands[i].command);
186               printf("%s\n", strtimes);
187               times = atoi (strtimes);
188             }
189         }
190     }
191
192   if(done)
193     {
194       int j;
195       for (j = 0; j < times; j++)
196         {
197           func (web_view);
198         }
199     }
200   else
201     {
202       if (!strncmp ("http://", command, 7))
203         {
204           printf ("Loading URI \"%s\"\n", command);
205           uri = command;
206           webkit_web_view_load_uri (web_view, uri);
207         }
208     }
209 }
210
211 static void *control_fifo()
212 {
213   if (fifodir) {
214     sprintf(fifopath, "%s/uzbl_%d", fifodir, getpid());
215   } else {
216     sprintf(fifopath, "/tmp/uzbl_%d", getpid());
217   }
218
219   if (mkfifo (fifopath, 0666) == -1) {
220     printf("Possible error creating fifo\n");
221   }
222
223   if (mechmode) {
224     printf("%s\n", fifopath);
225   } else {
226     printf ("Opened control fifo in %s\n", fifopath);
227   }
228
229   while (true)
230   {
231     FILE *fifo = fopen(fifopath, "r");
232     if (!fifo) {
233       printf("Could not open %s for reading\n", fifopath);
234       return NULL;
235     }
236
237     char buffer[256];
238     memset(buffer, 0, sizeof(buffer));
239     while (!feof(fifo) && fgets(buffer, sizeof(buffer), fifo)) {
240       if (strcmp(buffer, "\n")) {
241         buffer[strlen(buffer)-1] = '\0'; // Remove newline
242         parse_command(buffer);
243       }
244     }
245   }
246
247   return NULL;
248 }
249
250 static void add_command (char* cmdstr, void* function)
251 {
252   strncpy(commands[numcmds].command, cmdstr, strlen(cmdstr));
253   commands[numcmds].func = function;
254   numcmds++;
255 }
256
257 static bool setup_gtk (int argc, char* argv[])
258 {
259   gtk_init (&argc, &argv);
260
261   GtkWidget* vbox = gtk_vbox_new (FALSE, 0);
262   gtk_box_pack_start (GTK_BOX (vbox), create_browser (), TRUE, TRUE, 0);
263   modeline = create_modeline ();
264   gtk_box_pack_start (GTK_BOX (vbox), modeline, FALSE, FALSE, 0);
265
266   main_window = create_window ();
267   gtk_container_add (GTK_CONTAINER (main_window), vbox);
268   GError *error = NULL;
269
270   GOptionContext* context = g_option_context_new ("- The Usable Browser, controlled entirely through a FIFO");
271   g_option_context_add_main_entries (context, entries, NULL);
272   g_option_context_add_group (context, gtk_get_option_group (TRUE));
273   g_option_context_parse (context, &argc, &argv, &error);
274
275   if (uri)
276     {
277       webkit_web_view_load_uri (web_view, uri);
278     }
279
280   gtk_widget_grab_focus (GTK_WIDGET (web_view));
281   gtk_widget_show_all (main_window);
282   gtk_widget_hide(modeline);
283
284   return true;
285 }
286
287 static void setup_commands ()
288 {
289   //This func. is nice but currently it cannot be used for functions that require arguments or return data. --sentientswitch
290
291   add_command("b",  &webkit_web_view_go_back);
292   add_command("f",  &webkit_web_view_go_forward);
293   add_command("r",  &webkit_web_view_reload); //Buggy
294   add_command("s",  &webkit_web_view_stop_loading);
295   add_command("z+", &webkit_web_view_zoom_in); //Can crash (when max zoom reached?).
296   add_command("z-", &webkit_web_view_zoom_out); //Crashes as zoom +
297   //add_command("get uri", &webkit_web_view_get_uri);
298 }
299
300 static void setup_threading ()
301 {
302   pthread_t control_thread;
303   pthread_create(&control_thread, NULL, control_fifo, NULL);
304 }
305
306 static void setup_settings ()
307 {
308   GKeyFile* config = g_key_file_new ();
309   gboolean  res    = g_key_file_load_from_file (config, "./config", G_KEY_FILE_NONE, NULL); //TODO: pass config file as argument
310
311   if (res)
312     {
313       printf ("Config loaded\n");
314     }
315   else
316     {
317       fprintf (stderr, "config loading failed\n"); //TODO: exit codes with gtk? 
318     }
319
320   history_file = g_key_file_get_value (config, "behavior", "history_file", NULL);
321   if (history_file)
322     {
323       printf ("Setting history file to: %s\n", history_file);
324     }
325   else
326     {
327       printf ("History logging disabled\n");
328     }
329
330   home_page = g_key_file_get_value (config, "behavior", "home_page", NULL);
331   if (home_page)
332     {
333       printf ("Setting home page to: %s\n", home_page);
334     }
335   else
336     {
337       printf ("Home page disabled\n");
338     }
339 }
340
341 int main (int argc, char* argv[])
342 {
343   if (!g_thread_supported ())
344     g_thread_init (NULL);
345
346   setup_settings ();
347   setup_gtk (argc, argv);
348   setup_commands ();
349   setup_threading ();
350   gtk_main ();
351
352   printf ("Shutting down...\n");
353
354   unlink (fifopath);
355
356   return 0;
357 }