Start of project.
authorDaniel Carl <danielcarl@gmx.de>
Tue, 18 Sep 2012 23:20:32 +0000 (01:20 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Sun, 30 Sep 2012 12:23:11 +0000 (14:23 +0200)
Makefile [new file with mode: 0644]
config.mk [new file with mode: 0644]
src/callback.c [new file with mode: 0644]
src/callback.h [new file with mode: 0644]
src/command.c [new file with mode: 0644]
src/command.h [new file with mode: 0644]
src/config.h [new file with mode: 0644]
src/keybind.c [new file with mode: 0644]
src/keybind.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/main.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1ea23d6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+include config.mk
+
+all: $(TARGET)
+
+-include $(DEPS)
+
+options:
+       @echo "$(PROJECT) build options:"
+       @echo "LIBS      = $(LIBS)"
+       @echo "CC        = $(CC)"
+       @echo "CFLAGS    = $(CFLAGS)"
+       @echo "CPPFLAGS  = $(CPPFLAGS)"
+       @echo "LDFLAGS   = $(LDFLAGS)"
+
+all: $(TARGET)
+
+$(TARGET): $(OBJ)
+       @echo "$(CC) $@"
+       @$(CC) $(OBJ) -o $(TARGET) $(LDFLAGS)
+
+%.o: %.c $(HEAD)
+       @echo "${CC} $<"
+       @$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS)
+
+debug: $(DTARGET)
+
+$(DTARGET): $(DOBJ)
+       @echo "$(CC) $@"
+       @$(CC) $(DFLAGS) $(DOBJ) -o $(DTARGET) $(LDFLAGS)
+
+%.do: %.c $(HEAD)
+       @echo "${CC} $<"
+       @$(CC) -c -o $@ $< $(CPPFLAGS) $(DFLAGS)
+
+clean:
+       $(RM) $(OBJ) $(DOBJ) $(TARGET) $(DTARGET)
+
+dist: distclean
+       @echo "Creating tarball."
+       @git archive --format tar -o $(DIST_FILE) HEAD
+
+distclean:
+       $(RM) $(DIST_FILE)
+
+.PHONY: clean debug all install uninstall options dist
diff --git a/config.mk b/config.mk
new file mode 100644 (file)
index 0000000..d0a5fb0
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,45 @@
+#----------------user/install options----------------
+VERSION = 0.1.0
+
+PROJECT = vimprobable
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin/
+MANDIR ?= $(PREFIX)/share/man/
+
+#----------------compile options---------------------
+LIBS = gtk+-2.0 webkit-1.0
+
+CFLAGS += $(shell pkg-config --cflags $(LIBS))
+CFLAGS += -Wall
+CFLAGS += -pipe
+CFLAGS += -ansi
+CFLAGS += -std=c99
+CFLAGS += -pedantic
+CFLAGS += -Wmissing-declarations
+CFLAGS += -Wmissing-parameter-type
+#CFLAGS += -Wstrict-prototypes
+
+LDFLAGS += $(shell pkg-config --libs $(LIBS)) -lX11 -lXext
+
+CPPFLAGS += -DPROJECT=\"$(PROJECT)\"
+CPPFLAGS += -DVERSION=\"${VERSION}\"
+
+#----------------developer options-------------------
+DFLAGS += $(CFLAGS)
+DFLAGS += -DDEBUG
+DFLAGS += -ggdb
+DFLAGS += -g
+
+#----------------end of options----------------------
+OBJ       = $(patsubst %.c, %.o, $(wildcard src/*.c))
+DOBJ      = $(patsubst %.c, %.do, $(wildcard src/*.c))
+HEAD      = $(wildcard *.h)
+DEPS      = $(OBJ:%.o=%.d)
+
+TARGET    = $(PROJECT)
+DTARGET   = $(TARGET)_dbg
+DIST_FILE = $(PROJECT)_$(VERSION).tar.gz
+
+FMOD = 0644
+
+MFLAGS = --no-print-directory
diff --git a/src/callback.c b/src/callback.c
new file mode 100644 (file)
index 0000000..9f2b099
--- /dev/null
@@ -0,0 +1,31 @@
+#include "main.h"
+#include "callback.h"
+#include "config.h"
+
+void webview_load_status_cb(WebKitWebView* view, GParamSpec* pspec)
+{
+    Gui* gui        = &vp.gui;
+    const char* uri = webkit_web_view_get_uri(gui->webview);
+
+    switch (webkit_web_view_get_load_status(gui->webview)) {
+        case WEBKIT_LOAD_COMMITTED:
+            vp_update_urlbar(uri);
+            break;
+
+        case WEBKIT_LOAD_FINISHED:
+            break;
+
+        default:
+            break;
+    }
+}
+
+void destroy_window_cb(GtkWidget* widget, GtkWidget* window)
+{
+    vp_close_browser(0);
+}
+
+gboolean dummy_cb(void)
+{
+    return TRUE;
+}
diff --git a/src/callback.h b/src/callback.h
new file mode 100644 (file)
index 0000000..d2839ae
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+/* callbacks */
+void webview_load_status_cb(WebKitWebView* view, GParamSpec* pspec);
+void destroy_window_cb(GtkWidget* widget, GtkWidget* window);
+gboolean dummy_cb(void);
+
+#endif /* end of include guard: CALLBACKS_H */
diff --git a/src/command.c b/src/command.c
new file mode 100644 (file)
index 0000000..55e0255
--- /dev/null
@@ -0,0 +1,89 @@
+#include "command.h"
+
+static CommandInfo cmd_list[] = {
+    /* command   function */
+    {"quit",     quit},
+    {"source",   view_source},
+};
+
+static void command_sharg_append(GArray* a, const gchar* str);
+
+
+void command_init()
+{
+    guint i;
+    vp.behave.commands = g_hash_table_new(g_str_hash, g_str_equal);
+
+    for (i = 0; i < LENGTH(cmd_list); i++) {
+        g_hash_table_insert(vp.behave.commands, (gpointer)cmd_list[i].name, &cmd_list[i]);
+    }
+}
+
+void command_parse_line(const gchar* line, GString* result)
+{
+    gchar* string = g_strdup(line);
+
+    /* strip trailing newline, and any other whitespace from left */
+    g_strstrip(string);
+
+    if (strcmp(string, "")) {
+        /* ignore comment lines */
+        if ((string[0] != '#')) {
+            GArray* a = g_array_new(TRUE, FALSE, sizeof(gchar*));
+            const CommandInfo* c = command_parse_parts(string, a);
+            if (c) {
+                command_run_command(c, a, result);
+            }
+            g_array_free(a, TRUE);
+        }
+    }
+
+    g_free(string);
+}
+
+/* static? */
+const CommandInfo* command_parse_parts(const gchar* line, GArray* a)
+{
+    CommandInfo* c = NULL;
+
+    /* split the line into the command and its parameters */
+    gchar **tokens = g_strsplit(line, " ", 2);
+
+    /* look up the command */
+    c = g_hash_table_lookup(vp.behave.commands, tokens[0]);
+    if (!c) {
+        g_strfreev(tokens);
+        return NULL;
+    }
+
+    gchar* p = g_strdup(tokens[1]);
+    command_sharg_append(a, p);
+
+    g_strfreev(tokens);
+    g_free(p);
+
+    return c;
+}
+
+void command_run_command(const CommandInfo* c, GArray* a, GString* result)
+{
+    c->function(a, result);
+}
+
+static void command_sharg_append(GArray* a, const gchar* str)
+{
+    const gchar* s = (str ? str : "");
+    g_array_append_val(a, s);
+}
+
+void quit(GArray* argv, GString* result)
+{
+    vp_close_browser(); 
+}
+
+void view_source(GArray* argv, GString* result)
+{
+    gboolean mode = webkit_web_view_get_view_source_mode(vp.gui.webview);
+    webkit_web_view_set_view_source_mode(vp.gui.webview, !mode);
+    webkit_web_view_reload(vp.gui.webview);
+}
diff --git a/src/command.h b/src/command.h
new file mode 100644 (file)
index 0000000..36acfc7
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include "main.h"
+#include <webkit/webkit.h>
+
+typedef void (*Command)(GArray *argv, GString *result);
+
+typedef struct {
+    const gchar* name;
+    Command      function;
+} CommandInfo;
+
+
+void command_init(void);
+void command_parse_line(const gchar* line, GString* result);
+const CommandInfo* command_parse_parts(const gchar* line, GArray* a);
+void command_run_command(const CommandInfo* c, GArray* a, GString* result);
+
+void quit(GArray* argv, GString* result);
+void view_source(GArray* argv, GString* result);
+
+#endif /* end of include guard: COMMAND_H */
diff --git a/src/config.h b/src/config.h
new file mode 100644 (file)
index 0000000..42e6b51
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#define START_PAGE      "http://sourceforge.net/apps/trac/vimprobable"
+
+#define STATUS_BG_COLOR "#000000"           /* background color for status bar */
+#define STATUS_FG_COLOR "#ffffff"           /* foreground color for status bar */
+#define STATUS_BAR_FONT "monospace bold 8"
+#define URL_BOX_FONT    "monospace 8"
+
+#endif /* end of include guard: CONFIG_H */
diff --git a/src/keybind.c b/src/keybind.c
new file mode 100644 (file)
index 0000000..a79cb5f
--- /dev/null
@@ -0,0 +1,100 @@
+#include "main.h"
+#include "keybind.h"
+#include "command.h"
+
+static GSList* keys = NULL;
+static GString* modkeys = NULL;
+
+static gboolean keybind_keypress_callback(WebKitWebView* webview, GdkEventKey* event);
+
+
+void keybind_init(void)
+{
+    modkeys = g_string_new("");
+    g_signal_connect(G_OBJECT(vp.gui.webview), "key-press-event", G_CALLBACK(keybind_keypress_callback), NULL);
+}
+
+void keybind_add(int mode, guint modkey, guint modmask, guint keyval, const gchar* command)
+{
+    struct _keybind_key* keybind = g_new0(struct _keybind_key, 1);
+
+    keybind->mode    = mode;
+    keybind->modkey  = modkey;
+    keybind->modmask = modmask;
+    keybind->keyval  = keyval;
+    keybind->command = g_strdup(command);
+
+    keys = g_slist_append(keys, keybind);
+
+     /* save the modkey also in the modkey string */
+    if (modkey) {
+        g_string_append_c(modkeys, modkey);
+    }
+}
+
+static gboolean keybind_keypress_callback(WebKitWebView* webview, GdkEventKey* event)
+{
+    GSList* tmp;
+    GdkModifierType irrelevant;
+    guint keyval;
+    static GdkKeymap *keymap;
+
+    keymap = gdk_keymap_get_default();
+
+    /* Get a mask of modifiers that shouldn't be considered for this event.
+     * E.g.: It shouldn't matter whether ';' is shifted or not. */
+    gdk_keymap_translate_keyboard_state(keymap, event->hardware_keycode,
+            event->state, event->group, &keyval, NULL, NULL, &irrelevant);
+
+    /* check for escape or modkeys or counts */
+    if ((CLEAN(event->state) & ~irrelevant) == 0) {
+        if (IS_ESCAPE(event)) {
+            /* TODO add function to main.c to switch the mode */
+            /* switch to normal mode */
+            vp.state.mode = VP_MODE_NORMAL;
+            /* remove current modkey and set count back to 0 */
+            vp.state.modkey = 0;
+            vp.state.count  = 0;
+            vp_update_statusbar();
+
+            return TRUE;
+        } else if (vp.state.modkey == 0 && ((event->keyval >= GDK_1 && event->keyval <= GDK_9)
+                || (event->keyval == GDK_0 && vp.state.count))) {
+            /* append the new entered count to previous one */
+            vp.state.count = (vp.state.count ? vp.state.count * 10 : 0) + (event->keyval - GDK_0);
+            vp_update_statusbar();
+
+            return TRUE;
+        } else if (strchr(modkeys->str, event->keyval) && vp.state.modkey != event->keyval) {
+            vp.state.modkey = (gchar)event->keyval;
+            vp_update_statusbar();
+
+            return TRUE;
+        }
+    }
+    /* check for keybinding */
+    for (tmp = keys; tmp != NULL; tmp = tmp->next) {
+        struct _keybind_key* keybind = (struct _keybind_key*)tmp->data;
+
+        if (gdk_keyval_to_lower(event->keyval) == keybind->keyval
+            && (event->state & keybind->modmask) == keybind->modmask
+            && keybind->modkey == vp.state.modkey
+            && keybind->command
+        ) {
+            GArray* a = g_array_new(TRUE, FALSE, sizeof(gchar*));
+            const CommandInfo* c = command_parse_parts(keybind->command, a);
+            if (c) {
+                command_run_command(c, a, NULL);
+            }
+            g_array_free(a, TRUE);
+
+            /* if key binding used, remove the modkey */
+            vp.state.modkey = 0;
+            vp_update_statusbar();
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
diff --git a/src/keybind.h b/src/keybind.h
new file mode 100644 (file)
index 0000000..3758512
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef KEYBIND_H
+#define KEYBIND_H
+
+#include <gdk/gdkkeysyms.h>
+
+/* the CLEAN_MOD_*_MASK defines have all the bits set that will be stripped from the modifier bit field */
+#define CLEAN_MOD_NUMLOCK_MASK (GDK_MOD2_MASK)
+#define CLEAN_MOD_BUTTON_MASK (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_BUTTON4_MASK|GDK_BUTTON5_MASK)
+
+/* remove unused bits, numlock symbol and buttons from keymask */
+#define CLEAN(mask) (mask & (GDK_MODIFIER_MASK) & ~(CLEAN_MOD_NUMLOCK_MASK) & ~(CLEAN_MOD_BUTTON_MASK))
+#define IS_ESCAPE(event) (IS_ESCAPE_KEY(CLEAN(event->state), event->keyval))
+#define IS_ESCAPE_KEY(s, k) ((s == 0 && k == GDK_Escape) || \
+        (s == GDK_CONTROL_MASK && k == GDK_bracketleft))
+
+struct _keybind_key {
+    int    mode;        /* mode maks for allowed browser modes */
+    guint  modkey;
+    guint  modmask;     /* modemask for the kayval */
+    guint  keyval;
+    gchar* command;     /* command to run */
+};
+
+void keybind_init(void);
+void keybind_add(int mode, guint modkey, guint modmask, guint keyval, const gchar* command);
+
+#endif /* end of include guard: KEYBIND_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..1f27c48
--- /dev/null
@@ -0,0 +1,232 @@
+#include "config.h"
+#include "main.h"
+#include "command.h"
+#include "callback.h"
+#include "keybind.h"
+
+/* variables */
+VpCore vp;
+
+/* functions */
+static void vp_print_version(void);
+static void vp_init(void);
+static void vp_init_gui(void);
+static void vp_setup_signals(void);
+
+
+gboolean vp_load_uri(const Arg* arg)
+{
+    char* u;
+    const char* uri = arg->c;
+
+    if (strcmp(uri, "") == 0) {
+        return FALSE;
+    }
+    u = g_strrstr(uri, "://") ? g_strdup(uri) : g_strdup_printf("http://%s", uri);
+
+    /* Load a web page into the browser instance */
+    webkit_web_view_load_uri(vp.gui.webview, u);
+    g_free(u);
+
+    /* change state to normal mode */
+    vp.state.mode = VP_MODE_NORMAL;
+    vp_update_statusbar();
+
+    return TRUE;
+}
+
+gboolean vp_navigate(const Arg *arg)
+{
+    if (arg->i <= NAVIG_FORWARD) {
+        /* TODO allow to set a count for the navigation */
+        webkit_web_view_go_back_or_forward(
+            vp.gui.webview, (arg->i == NAVIG_BACK ? -1 : 1)
+        );
+    } else if (arg->i == NAVIG_RELOAD) {
+        webkit_web_view_reload(vp.gui.webview);
+    } else if (arg->i == NAVIG_RELOAD_FORCE) {
+        webkit_web_view_reload_bypass_cache(vp.gui.webview);
+    } else {
+        webkit_web_view_stop_loading(vp.gui.webview);
+    }
+
+    return TRUE;
+}
+
+void vp_close_browser()
+{
+    if (vp.behave.commands) {
+        g_hash_table_destroy(vp.behave.commands);
+        vp.behave.commands = NULL;
+    }
+    gtk_main_quit();
+}
+
+void vp_update_urlbar(const gchar* uri)
+{
+    gchar* markup;
+
+    markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", STATUS_BAR_FONT, uri);
+    gtk_label_set_markup(GTK_LABEL(vp.gui.statusbar.left), markup);
+    g_free(markup);
+}
+
+void vp_update_statusbar(void)
+{
+    GString* status = g_string_new("");
+    gchar*   markup;
+
+    /* TODO show modekeys count */
+    /* at the moment we have only one modkey available */
+    /* http://current_uri.tld   34% */
+    g_string_append_printf(status, "%.0d", vp.state.count);
+    if (vp.state.modkey) {
+        g_string_append_c(status, vp.state.modkey);
+    }
+    g_string_append_printf(status, " %.0d%%", 46);
+    markup = g_markup_printf_escaped("<span font=\"%s\">%s</span>", STATUS_BAR_FONT, status->str);
+    gtk_label_set_markup(GTK_LABEL(vp.gui.statusbar.right), markup);
+    g_free(markup);
+}
+
+static void vp_print_version(void)
+{
+    fprintf(stderr, "%s/%s (build %s %s)\n", VERSION, PROJECT, __DATE__, __TIME__);
+}
+
+static void vp_init(void)
+{
+    /* initialize the gui elements and event callbacks */
+    vp_init_gui();
+    /* initialize the commands hash map */
+    command_init();
+    /* initialize the keybindings */
+    keybind_init();
+
+    /*command_parse_line("quit", NULL);*/
+    keybind_add(VP_MODE_NORMAL, GDK_g, 0, GDK_f, "source");
+    keybind_add(VP_MODE_NORMAL, 0,     0, GDK_d, "quit");
+}
+
+static void vp_init_gui(void)
+{
+    Gui* gui = &vp.gui;
+    GdkColor bg, fg;
+
+    GdkGeometry hints = {10, 10};
+    gui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_wmclass(GTK_WINDOW(gui->window), PROJECT, PROJECT);
+    gtk_window_set_default_size(GTK_WINDOW(gui->window), 640, 480);
+    gtk_window_set_title(GTK_WINDOW(gui->window), PROJECT);
+    gtk_window_set_geometry_hints(GTK_WINDOW(gui->window), NULL, &hints, GDK_HINT_MIN_SIZE);
+
+    /* Create a browser instance */
+    gui->webview = WEBKIT_WEB_VIEW(webkit_web_view_new());
+
+    /* Create a scrollable area */
+    gui->viewport = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(
+        GTK_SCROLLED_WINDOW(gui->viewport),
+        GTK_POLICY_NEVER, GTK_POLICY_NEVER
+    );
+
+    gui->box = GTK_BOX(gtk_vbox_new(FALSE, 0));
+
+    /* Prepare the imputbox */
+    gui->inputbox = gtk_entry_new();
+    gtk_entry_set_inner_border(GTK_ENTRY(gui->inputbox), NULL);
+    g_object_set(gtk_widget_get_settings(gui->inputbox), "gtk-entry-select-on-focus", FALSE, NULL);
+
+    PangoFontDescription* font = pango_font_description_from_string(URL_BOX_FONT);
+    gtk_widget_modify_font(GTK_WIDGET(gui->inputbox), font);
+    pango_font_description_free(font);
+
+    /* Prepare the statusbar */
+    gui->statusbar.box   = GTK_BOX(gtk_hbox_new(FALSE, 0));
+    gui->statusbar.left  = gtk_label_new(NULL);
+    gui->statusbar.right = gtk_label_new(NULL);
+
+    /* Prepare the event box */
+    gui->eventbox = gtk_event_box_new();
+    gdk_color_parse(STATUS_BG_COLOR, &bg);
+    gdk_color_parse(STATUS_FG_COLOR, &fg);
+    gtk_widget_modify_bg(gui->eventbox, GTK_STATE_NORMAL, &bg);
+    gtk_widget_modify_fg(gui->eventbox, GTK_STATE_NORMAL, &fg);
+
+    vp_setup_signals();
+
+    /* Put all part together */
+    gtk_container_add(GTK_CONTAINER(gui->viewport), GTK_WIDGET(gui->webview));
+    gtk_container_add(GTK_CONTAINER(gui->eventbox), GTK_WIDGET(gui->statusbar.box));
+    gtk_container_add(GTK_CONTAINER(gui->window), GTK_WIDGET(gui->box));
+    gtk_misc_set_alignment(GTK_MISC(gui->statusbar.left), 0.0, 0.0);
+    gtk_misc_set_alignment(GTK_MISC(gui->statusbar.right), 1.0, 0.0);
+    gtk_box_pack_start(gui->statusbar.box, gui->statusbar.left, TRUE, TRUE, 2);
+    gtk_box_pack_start(gui->statusbar.box, gui->statusbar.right, FALSE, FALSE, 2);
+    gtk_widget_modify_bg(GTK_WIDGET(gui->statusbar.left), GTK_STATE_NORMAL, &bg);
+    gtk_widget_modify_fg(GTK_WIDGET(gui->statusbar.left), GTK_STATE_NORMAL, &fg);
+    gtk_widget_modify_bg(GTK_WIDGET(gui->statusbar.right), GTK_STATE_NORMAL, &bg);
+    gtk_widget_modify_fg(GTK_WIDGET(gui->statusbar.right), GTK_STATE_NORMAL, &fg);
+
+    gtk_box_pack_start(gui->box, gui->viewport, TRUE, TRUE, 0);
+    gtk_box_pack_start(gui->box, gui->eventbox, FALSE, FALSE, 0);
+    gtk_entry_set_has_frame(GTK_ENTRY(gui->inputbox), FALSE);
+    gtk_box_pack_end(gui->box, gui->inputbox, FALSE, FALSE, 0);
+
+    /* Make sure that when the browser area becomes visible, it will get mouse
+     * and keyboard events */
+    gtk_widget_grab_focus(GTK_WIDGET(gui->webview));
+
+    /* Make sure the main window and all its contents are visible */
+    gtk_widget_show_all(gui->window);
+}
+
+static void vp_setup_signals(void)
+{
+    Gui* gui              = &vp.gui;
+    WebKitWebFrame *frame = webkit_web_view_get_main_frame(gui->webview);
+
+    /* Set up callbacks so that if either the main window or the browser
+     * instance is closed, the program will exit */
+    g_signal_connect(gui->window, "destroy", G_CALLBACK(destroy_window_cb), NULL);
+    g_signal_connect(G_OBJECT(frame), "scrollbars-policy-changed", G_CALLBACK(dummy_cb), NULL);
+    g_signal_connect(G_OBJECT(gui->webview), "notify::load-status", G_CALLBACK(webview_load_status_cb), NULL);
+}
+
+int main(int argc, char* argv[])
+{
+    static gboolean ver = false;
+    static GError* err;
+    static GOptionEntry opts[] = {
+        { "version", 'v', 0, G_OPTION_ARG_NONE, &ver, "print version", NULL },
+        { NULL }
+    };
+    /* Initialize GTK+ */
+    if (!gtk_init_with_args(&argc, &argv, "[<uri>]", opts, NULL, &err)) {
+        g_printerr("can't init gtk: %s\n", err->message);
+        g_error_free(err);
+
+        return EXIT_FAILURE;
+    }
+
+    if (ver) {
+        vp_print_version();
+        return EXIT_SUCCESS;
+    }
+    vp_init();
+
+    /* command line argument: URL */
+    Arg arg;
+    if (argc > 1) {
+        arg.c = g_strdup(argv[argc - 1]);
+    } else {
+        arg.c = g_strdup(START_PAGE);
+    }
+    vp_load_uri(&arg);
+    g_free(arg.c);
+
+    /* Run the main GTK+ event loop */
+    gtk_main();
+
+    return EXIT_SUCCESS;
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644 (file)
index 0000000..67cc893
--- /dev/null
@@ -0,0 +1,102 @@
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <webkit/webkit.h>
+
+#define LENGTH(x) (sizeof x / sizeof x[0])
+
+#ifdef DEBUG
+#define PRINT_DEBUG(...) do { \
+    fprintf(stderr, "\n\033[31;1mDEBUG:\033[0m %s:%d:%s()\t", __FILE__, __LINE__, __func__); \
+    fprintf(stderr, __VA_ARGS__);\
+    fprintf(stderr, "\n"); \
+} while(0);
+#define TIMER_START GTimer *__timer; do {__timer = g_timer_new(); g_timer_start(__timer);} while(0)
+#define TIMER_END do {gulong __debug_micro = 0; gdouble __debug_elapsed = g_timer_elapsed(__timer, &__debug_micro);\
+    PRINT_DEBUG("\033[33mtimer:\033[0m elapsed: %f, micro: %lu", __debug_elapsed, __debug_micro);\
+    g_timer_destroy(__timer); \
+} while(0)
+#else
+#define PRINT_DEBUG(message, ...)
+#define TIMER_START
+#define TIMER_END
+#endif
+
+/* enums */
+typedef enum _vp_mode {
+    VP_MODE_NORMAL       = (1 << 0),
+    VP_MODE_PATH_THROUGH = (1 << 1),
+    VP_MODE_INSERT       = (1 << 2),
+} Mode;
+
+enum {
+    NAVIG_BACK,
+    NAVIG_FORWARD,
+    NAVIG_RELOAD,
+    NAVIG_RELOAD_FORCE,
+    NAVIG_STOP_LOADING
+};
+
+/* structs */
+typedef struct {
+    gint  i;
+    char* c;
+} Arg;
+
+/* statusbar */
+typedef struct {
+    GtkBox*     box;
+    GtkWidget*  left;
+    GtkWidget*  right;
+} StatusBar;
+
+/* gui */
+typedef struct {
+    GtkWidget*     window;
+    WebKitWebView* webview;
+    GtkWidget*     viewport;
+    GtkBox*        box;
+    GtkWidget*     eventbox;
+    GtkWidget*     inputbox;
+    StatusBar      statusbar;
+} Gui;
+
+/* state */
+typedef struct {
+    Mode          mode;
+    gchar         modkey;
+    int           count;
+} State;
+
+/* behaviour */
+typedef struct {
+    /* command list: (key)name -> (value)Command  */
+    GHashTable* commands;
+} Behaviour;
+
+/* core struct */
+typedef struct {
+    Gui           gui;
+    State         state;
+    Behaviour     behave;
+#if 0
+    Network       net;
+    Ssl           ssl;
+    Communication comm;
+    Info          info;
+#endif
+} VpCore;
+
+/* main object */
+extern VpCore vp;
+
+/* functions */
+void vp_update_statusbar(void);
+void vp_update_urlbar(const gchar* uri);
+gboolean vp_load_uri(const Arg* arg);
+gboolean vp_navigate(const Arg* arg);
+void vp_close_browser();
+
+#endif /* end of include guard: MAIN_H */