// Simple browser my attempt at an even less-sucking browser than surf. //original code is from alot of places, gtkforums.com, gtkwebkit.org provided //alot of nice API examples :), and of course, surf (though i was careful not to steal any code because of the damned MIT/X license) //uzbl also helped inspire me. along with a previous surf fork named inferno. #include #include #include #include #include #include #include #include typedef struct tab { GtkWidget *scroll; GtkWidget *label; gchar *main_title; gchar *url_entry; gint load_progress; guint status_context_id; WebKitWebView *view; } tab; static struct { WebKitWebSettings *webkitsettings; WebKitWebWindowFeatures *webkitwindowfeatures; SoupSession *session; SoupCookieJar *jar; gboolean hide; GtkWidget *win; GtkWidget *bar; GtkWidget *search; GtkWidget *vbox; GtkNotebook *notebook; GtkWidget *status; } w; #define get_tab(x, page_idx ) (struct tab*)g_object_get_qdata(G_OBJECT( gtk_notebook_get_nth_page( (GtkNotebook*)w.notebook, page_idx ) ), term_data_id); static GQuark term_data_id = 0; static void cb_entry(GtkWidget* entry, gpointer data); static void cb_go(gboolean b); static void cb_download(WebKitWebView *web_view, GObject *download, gpointer user_data); static void link_hover(WebKitWebView* page, const gchar* title, const gchar* link, gpointer data); static void cb_title_changed(WebKitWebView *v, WebKitWebFrame *f, const char *title, tab *t); static void load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, tab *t); static void load_uri(gchar *uri); static void tab_new(gboolean b); static void tab_zoom(gboolean b); static void tab_close(); static void tab_and_go(); static void tab_focus(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer user_data); static void tab_view_source(); static void tab_reload(); static void window_setup(); static void search(GtkEntry *entry, gboolean b); static void toggle(); static void show_search(gboolean b); static void focus_view(); WebKitWebView * tab_new_requested(WebKitWebView *v, WebKitWebFrame *f); static void tab_switch(gboolean b); gboolean cb_keypress(GtkWidget *widget, GdkEventKey *event); gchar* tab_get_tab_postition() { gchar *page_info = NULL; sprintf(page_info, "[ %d / %d ]", gtk_notebook_get_current_page(w.notebook), gtk_notebook_get_n_pages(w.notebook)); puts(page_info); return(page_info); } static void search(GtkEntry *entry, gboolean b) { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); webkit_web_view_search_text(t->view, gtk_entry_get_text(GTK_ENTRY(w.search)), FALSE, b, TRUE); } static void show_search(gboolean b) { if(b) { gtk_widget_show(w.search); gtk_widget_grab_focus(w.search); } else { gtk_widget_hide(w.search); } } /* Called when URL is returned from the entry bar */ static void cb_entry (GtkWidget* entry, gpointer data) { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); load_uri(g_strdup(gtk_entry_get_text (GTK_ENTRY (w.bar)))); gtk_widget_grab_focus(GTK_WIDGET(t->view)); if(w.hide) { gtk_widget_hide(w.bar); } w.hide = FALSE; } /* Basic reload function */ static void tab_reload() { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); webkit_web_view_reload(t->view); } /* close tab, and quit if there are no tabs */ static void tab_close() { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); gtk_notebook_remove_page(w.notebook, gtk_notebook_get_current_page(w.notebook)); g_free(t); if(gtk_notebook_get_n_pages(w.notebook) == 1) { gtk_notebook_set_show_tabs(w.notebook, FALSE); gtk_widget_grab_focus(gtk_notebook_get_nth_page(w.notebook, gtk_notebook_get_current_page(w.notebook))); focus_view(); } if (gtk_notebook_get_n_pages(w.notebook) == 0) { gtk_main_quit(); } } /* download with external command */ /* NEEDS WORK, should use sprintf to insert different values, 1) Location 2) File name */ static void cb_download(WebKitWebView *web_view, GObject *d, gpointer user_data) { const gchar *c = webkit_download_get_uri(WEBKIT_DOWNLOAD(d)); gchar *command = g_strconcat(DEFAULT_DOWNLOAD, g_get_home_dir(), "/ ", g_strdup(c), NULL); g_spawn_command_line_async(command, NULL); g_free(command); } /* link hovering callback */ static void link_hover (WebKitWebView* page, const gchar* title, const gchar* link, gpointer data) { if(link != NULL) { gtk_statusbar_push(GTK_STATUSBAR(w.status), 0, link); } else { gtk_statusbar_push(GTK_STATUSBAR(w.status), 0, ""); } } /* title change callback */ static void cb_title_changed(WebKitWebView *v, WebKitWebFrame *f, const char *title, tab *t) { gchar *tabtitle; if (gtk_notebook_get_current_page(w.notebook) == gtk_notebook_page_num(w.notebook, t->scroll)) { gtk_window_set_title(GTK_WINDOW(w.win), g_strconcat(title, NULL)); } if(strlen(title) < DEFAULT_TAB_LENGTH ) { tabtitle = g_strdup(title); } else { tabtitle = g_strndup(title, DEFAULT_TAB_LENGTH); strcat(tabtitle, "..."); } gtk_label_set_label(GTK_LABEL(t->label), tabtitle); } /* when the page load is commited, call this function */ static void load_commit_cb (WebKitWebView* page, WebKitWebFrame* frame, tab *t) { const gchar* uri = webkit_web_frame_get_uri(frame); if (gtk_notebook_get_current_page(w.notebook) == gtk_notebook_page_num(w.notebook, t->scroll)) { gtk_entry_set_text (GTK_ENTRY (w.bar), uri); } FILE *history = fopen(g_build_filename(g_get_home_dir(), DEFAULT_HISTORY_FILE, NULL), "a+"); fprintf(history, "%s \n", uri); fclose(history); } /* an alternative to the regular tab command, combines tabbing and history command into one */ static void tab_and_go() { gchar *returned; g_spawn_command_line_sync(g_strconcat("sh -c 'sort ", g_build_filename(g_get_home_dir(), ".sb_history", NULL), " | dmenu -l 15 -xs -c'", NULL), &returned, NULL, NULL, NULL); if(strcmp(returned, "") == 0) { focus_view(); } else { tab_new(FALSE); load_uri(returned); g_free(returned); } } /* loads the uri, check for the protocol sign */ static void load_uri(gchar *uri) { gchar *u; //Barrowed from surf, no point creating another method, this seems to work well struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); u = g_strrstr(uri, "://") ? g_strdup(uri) : g_strdup_printf("http://%s", uri); webkit_web_view_load_uri(t->view, u); g_free(u); }; /*increase or decrease the zoom of the page */ static void tab_zoom (gboolean b) { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); if (b) { webkit_web_view_set_zoom_level(t->view, (webkit_web_view_get_zoom_level(t->view) + DEFAULT_ZOOM_SIZE)); } else { webkit_web_view_set_zoom_level(t->view, (webkit_web_view_get_zoom_level(t->view) - DEFAULT_ZOOM_SIZE)); } } /* if the bar isn't visible, show it and set the w.hide flag to TRUE*/ static void grab_bar( ) { if(!gtk_widget_get_visible(w.bar)) { gtk_widget_show(w.bar); w.hide = TRUE; } gtk_widget_grab_focus(GTK_WIDGET(w.bar)); } /* go forward or backwards, simple enough */ static void cb_go(gboolean b) { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); if (b) { webkit_web_view_go_forward(t->view); } else { webkit_web_view_go_back(t->view); } } /* toggle visibility */ static void toggle() { if(gtk_widget_get_visible(w.bar)) { gtk_widget_hide(w.bar); gtk_widget_hide(w.status); gtk_notebook_set_show_tabs(w.notebook, FALSE); } else { gtk_widget_show_all(w.vbox); gtk_widget_hide(w.search); if (gtk_notebook_get_n_pages(w.notebook) == 1) { } else { gtk_notebook_set_show_tabs(w.notebook, TRUE);} } } /* rotate tabs forward or backwards */ static void tab_switch(gboolean b) { gint(current) = gtk_notebook_get_current_page(w.notebook); if(b) { if (current == gtk_notebook_get_n_pages(w.notebook) -1 ) { current = 0; } else { current = current + 1;} } else { if (current == 0) { current = gtk_notebook_get_n_pages(w.notebook) - 1; } else {current = current -1; } } gtk_notebook_set_current_page(w.notebook, current); } /* when a new tab is requested, return the t->view */ WebKitWebView * tab_new_requested(WebKitWebView *v, WebKitWebFrame *f) { puts("lol"); tab_new(FALSE); struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); return t->view; } /* switch to view source mode - stays in mode until reverted */ static void tab_view_source() { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); if(webkit_web_view_get_view_source_mode(t->view)) { webkit_web_view_set_view_source_mode(t->view, FALSE); } else { webkit_web_view_set_view_source_mode(t->view, TRUE); } webkit_web_view_reload(t->view); } /* create a tab */ static void tab_new(gboolean b) { tab *t; t = g_new0(tab, 1); t->scroll = gtk_scrolled_window_new(NULL, NULL); t->view = WEBKIT_WEB_VIEW(webkit_web_view_new ()); t->label = gtk_label_new("new tab"); /*reset status bar*/ gtk_container_add(GTK_CONTAINER(t->scroll), GTK_WIDGET(t->view)); int index = gtk_notebook_append_page(w.notebook, t->scroll, t->label); gtk_notebook_set_tab_reorderable(w.notebook, t->scroll, TRUE); //if ( gtk_notebook_get_n_pages(w.notebook) == 1) { } if (index == 0) { gtk_notebook_set_show_tabs(w.notebook, FALSE); } else { gtk_notebook_set_show_tabs(w.notebook, TRUE); } g_object_set(G_OBJECT(w.webkitsettings), "enable-page-cache", TRUE, NULL); g_object_set(G_OBJECT(w.webkitwindowfeatures), "scrollbar-visible", FALSE, NULL); g_object_set(G_OBJECT(w.webkitsettings), "enable-java-applet", FALSE, NULL); g_object_set(G_OBJECT(w.webkitsettings), "user-stylesheet-uri", "file:///home/calvin/.surf/style.css", NULL); //webkit_web_view_set_settings (WEBKIT_WEB_VIEW(t->view), w.webkitsettings); gtk_statusbar_push(GTK_STATUSBAR(w.status), 0, ""); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(t->scroll), GTK_POLICY_NEVER, GTK_POLICY_NEVER); webkit_web_view_set_zoom_level(t->view, .80); /*callbacks*/ g_signal_connect (G_OBJECT (t->view), "title-changed", G_CALLBACK (cb_title_changed), t); g_signal_connect (G_OBJECT (t->view), "load-committed", G_CALLBACK (load_commit_cb), t); g_signal_connect (G_OBJECT (t->view), "hovering-over-link", G_CALLBACK (link_hover), t->view); g_signal_connect (G_OBJECT (t->view), "download-requested", G_CALLBACK (cb_download), t->view); g_signal_connect (G_OBJECT (t->view), "create-web-view", G_CALLBACK (tab_new_requested), NULL); /*settings*/ g_object_set_qdata_full(G_OBJECT(gtk_notebook_get_nth_page((GtkNotebook*)w.notebook, index)), term_data_id, t, NULL); gtk_widget_show_all(w.win); gtk_widget_hide(w.search); if (!b) { gtk_notebook_set_current_page(w.notebook, index); } gtk_widget_grab_focus(w.bar); } /* call the history command. should we do it ASYNC?*/ static void history_command() { gchar *returned, *file; file = g_build_filename(g_get_home_dir(), DEFAULT_HISTORY_FILE, NULL); g_spawn_command_line_sync(g_strconcat("sh -c 'sort ", file, " | dmenu -l 15 -xs -c'", NULL), &returned, NULL, NULL, NULL); if(strcmp(returned, "") == 0) { focus_view(); } else { load_uri(returned); } g_free(returned); g_free(file); } /*focus on tab after switching, aka title, statusbar, view, etc */ static void tab_focus(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer user_data) { gtk_statusbar_push(GTK_STATUSBAR(w.status), 0, ""); struct tab *t = get_tab(NULL, page_num); const char *url = webkit_web_view_get_uri(t->view); const char *title = webkit_web_view_get_title(t->view); if (title == NULL && url == NULL) { title = "sb"; url = ""; } gtk_window_set_title(GTK_WINDOW(w.win), title); gtk_entry_set_text(GTK_ENTRY(w.bar), url); } /* focus on view */ static void focus_view() { struct tab *t = get_tab(NULL, gtk_notebook_get_current_page(w.notebook)); gtk_widget_grab_focus(GTK_WIDGET(t->view)); } /* misc functions to help initialization */ static void window_setup() { term_data_id = g_quark_from_static_string("s"); w.vbox = gtk_vbox_new(FALSE, 0); w.notebook = gtk_notebook_new(); w.bar = gtk_entry_new (); w.search = gtk_entry_new(); w.status = gtk_statusbar_new(); gtk_entry_set_has_frame(GTK_ENTRY(w.bar), FALSE); gtk_notebook_set_scrollable(w.notebook, TRUE) ; gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(w.status), FALSE); gtk_notebook_set_show_border(w.notebook, FALSE); gtk_notebook_set_tab_border(w.notebook, 0); gtk_box_pack_start(GTK_BOX (w.vbox), w.bar, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(w.vbox), GTK_WIDGET(w.notebook), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(w.vbox), w.search, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(w.vbox), w.status, FALSE, FALSE, 0); w.win = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(w.win), DEFAULT_HEIGHT, DEFAULT_WIDTH); w.webkitsettings = webkit_web_settings_new(); w.webkitwindowfeatures = webkit_web_window_features_new(); w.session = webkit_get_default_session(); w.jar = soup_cookie_jar_text_new(g_build_filename(g_get_home_dir(), DEFAULT_COOKIE_FILE, NULL), FALSE); soup_session_add_feature(w.session, SOUP_SESSION_FEATURE(w.jar)); tab_new(FALSE); g_signal_connect (G_OBJECT (w.search), "activate", G_CALLBACK (search), GINT_TO_POINTER(1)); g_signal_connect (G_OBJECT (w.bar), "activate", G_CALLBACK (cb_entry), NULL); g_signal_connect (G_OBJECT(w.notebook), "switch-page", G_CALLBACK(tab_focus), NULL); g_signal_connect (G_OBJECT (w.win), "destroy", G_CALLBACK (gtk_main_quit), NULL); g_signal_connect(w.win, "key-press-event", G_CALLBACK(cb_keypress), NULL); gtk_container_add (GTK_CONTAINER (w.win), w.vbox); gtk_widget_show_all (w.win); gtk_widget_hide(w.search); gtk_widget_grab_focus(w.bar); } /* key press callback function. NEEDED: a config.h where keys are defined */ gboolean cb_keypress (GtkWidget *widget, GdkEventKey *event) { guint(g) = event->keyval; if ( (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK ) { switch(g) { case GDK_l: grab_bar(); return TRUE; break; case GDK_f: show_search(TRUE); return TRUE; break; case GDK_g: tab_and_go(); return TRUE; break; case GDK_KEY_comma: cb_go(FALSE); return TRUE; break; case GDK_KEY_period: cb_go(TRUE); return TRUE; break; case GDK_o: history_command(); return TRUE; break; case GDK_h: toggle(); focus_view(); return TRUE; break; case GDK_Page_Up: tab_switch(FALSE); return TRUE; break; case GDK_Page_Down: tab_switch(TRUE); return TRUE; break; case GDK_t: tab_new(FALSE); gtk_widget_grab_focus(w.bar); return TRUE; break; case GDK_w: tab_close(); return TRUE; break; case GDK_bracketright: tab_zoom(TRUE); return TRUE; break; case GDK_bracketleft: tab_zoom(FALSE); return TRUE; break; case GDK_r: tab_reload(); return TRUE; break; case GDK_s: tab_view_source(); return TRUE; break; case GDK_Return: load_uri(g_strconcat(DEFAULT_SEARCH, gtk_entry_get_text(GTK_ENTRY(w.bar)), NULL)); return TRUE; break; default: return FALSE; break; } } if (gtk_widget_has_focus(w.bar) && g == GDK_Escape) { gtk_widget_grab_focus(GTK_WIDGET(w.notebook)); return TRUE; } if (gtk_widget_has_focus(w.search)) { if (g == GDK_Escape) { show_search(FALSE); focus_view(); return TRUE; } if ((g == GDK_Return) && (event->state & GDK_MOD1_MASK) == GDK_MOD1_MASK) { search(NULL, FALSE); } } return FALSE; } /* main function */ int main (int argc, char* argv[]) { gtk_init (&argc, &argv); window_setup(); if (argc == 2) { load_uri(argv[1]); } gtk_main(); return 0; }