/* Copyright (C) 2007-2008 Christian Dywan Copyright (C) 2008 Dale Whittaker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for the full license text. */ #if HAVE_CONFIG_H #include #endif #include "midori-addons.h" #include "midori-app.h" #include "midori-browser.h" #include "midori-console.h" #include "midori-extension.h" #include "midori-extensions.h" #include "midori-panel.h" #include "midori-preferences.h" #include "midori-plugins.h" #include "midori-stock.h" #include "midori-view.h" #include "midori-websettings.h" #include "sokoke.h" #include #include #include #include #include #ifdef HAVE_LIBXML #include #include #endif #ifdef HAVE_SQLITE #include #endif #if HAVE_LIBSOUP #include #endif #if ENABLE_NLS #include #include #endif #define MIDORI_HISTORY_ERROR g_quark_from_string("MIDORI_HISTORY_ERROR") typedef enum { MIDORI_HISTORY_ERROR_DB_OPEN, /* Error opening the database file */ MIDORI_HISTORY_ERROR_EXEC_SQL, /* Error executing SQL statement */ } MidoriHistoryError; static void stock_items_init (void) { typedef struct { const gchar* stock_id; const gchar* label; GdkModifierType modifier; guint keyval; const gchar* fallback; } FatStockItem; GtkIconSource* icon_source; GtkIconSet* icon_set; GtkIconFactory* factory = gtk_icon_factory_new (); gsize i; static FatStockItem items[] = { { STOCK_EXTENSION, NULL, 0, 0, GTK_STOCK_CONVERT }, { STOCK_NEWS_FEED, NULL, 0, 0, GTK_STOCK_INDEX }, { STOCK_SCRIPT, NULL, 0, 0, GTK_STOCK_EXECUTE }, { STOCK_STYLE, NULL, 0, 0, GTK_STOCK_SELECT_COLOR }, { STOCK_TRANSFER, NULL, 0, 0, GTK_STOCK_SAVE }, { STOCK_BOOKMARK, N_("_Bookmark"), 0, 0, GTK_STOCK_FILE }, { STOCK_BOOKMARKS, N_("_Bookmarks"), 0, 0, GTK_STOCK_DIRECTORY }, { STOCK_BOOKMARK_ADD, N_("_Add Bookmark"), 0, 0, GTK_STOCK_ADD }, { STOCK_CONSOLE, N_("_Console"), 0, 0, GTK_STOCK_DIALOG_WARNING }, { STOCK_EXTENSIONS, N_("_Extensions"), 0, 0, GTK_STOCK_CONVERT }, { STOCK_HISTORY, N_("_History"), 0, 0, GTK_STOCK_SORT_ASCENDING }, { STOCK_HOMEPAGE, N_("_Homepage"), 0, 0, GTK_STOCK_HOME }, { STOCK_SCRIPTS, N_("_Userscripts"), 0, 0, GTK_STOCK_EXECUTE }, { STOCK_STYLES, N_("User_styles"), 0, 0, GTK_STOCK_SELECT_COLOR }, { STOCK_TAB_NEW, N_("New _Tab"), 0, 0, GTK_STOCK_ADD }, { STOCK_TRANSFERS, N_("_Transfers"), 0, 0, GTK_STOCK_SAVE }, { STOCK_USER_TRASH, N_("_Closed Tabs and Windows"), 0, 0, "gtk-undo-ltr" }, { STOCK_WINDOW_NEW, N_("New _Window"), 0, 0, GTK_STOCK_ADD }, }; for (i = 0; i < G_N_ELEMENTS (items); i++) { icon_set = gtk_icon_set_new (); icon_source = gtk_icon_source_new (); if (items[i].fallback) { gtk_icon_source_set_icon_name (icon_source, items[i].fallback); items[i].fallback = NULL; gtk_icon_set_add_source (icon_set, icon_source); } gtk_icon_source_set_icon_name (icon_source, items[i].stock_id); gtk_icon_set_add_source (icon_set, icon_source); gtk_icon_source_free (icon_source); gtk_icon_factory_add (factory, items[i].stock_id, icon_set); gtk_icon_set_unref (icon_set); } gtk_stock_add ((GtkStockItem*)items, G_N_ELEMENTS (items)); gtk_icon_factory_add_default (factory); g_object_unref (factory); } static gchar* build_config_filename (const gchar* filename) { static gchar* path = NULL; if (!path) path = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL); g_mkdir_with_parents (path, 0700); return g_build_filename (path, filename, NULL); } static MidoriWebSettings* settings_new_from_file (const gchar* filename) { MidoriWebSettings* settings = midori_web_settings_new (); GKeyFile* key_file = g_key_file_new (); GError* error = NULL; GObjectClass* class; guint i, n_properties; GParamSpec** pspecs; GParamSpec* pspec; GType type; const gchar* property; gchar* str; gint integer; gfloat number; gboolean boolean; if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_KEEP_COMMENTS, &error)) { if (error->code != G_FILE_ERROR_NOENT) printf (_("The configuration couldn't be loaded: %s\n"), error->message); g_error_free (error); } class = G_OBJECT_GET_CLASS (settings); pspecs = g_object_class_list_properties (class, &n_properties); for (i = 0; i < n_properties; i++) { pspec = pspecs[i]; if (!(pspec->flags & G_PARAM_WRITABLE)) continue; type = G_PARAM_SPEC_TYPE (pspec); property = g_param_spec_get_name (pspec); if (type == G_TYPE_PARAM_STRING) { str = sokoke_key_file_get_string_default (key_file, "settings", property, G_PARAM_SPEC_STRING (pspec)->default_value, NULL); g_object_set (settings, property, str, NULL); g_free (str); } else if (type == G_TYPE_PARAM_INT) { integer = sokoke_key_file_get_integer_default (key_file, "settings", property, G_PARAM_SPEC_INT (pspec)->default_value, NULL); g_object_set (settings, property, integer, NULL); } else if (type == G_TYPE_PARAM_FLOAT) { number = sokoke_key_file_get_double_default (key_file, "settings", property, G_PARAM_SPEC_FLOAT (pspec)->default_value, NULL); g_object_set (settings, property, number, NULL); } else if (type == G_TYPE_PARAM_BOOLEAN) { boolean = sokoke_key_file_get_boolean_default (key_file, "settings", property, G_PARAM_SPEC_BOOLEAN (pspec)->default_value, NULL); g_object_set (settings, property, boolean, NULL); } else if (type == G_TYPE_PARAM_ENUM) { GEnumClass* enum_class = G_ENUM_CLASS ( g_type_class_ref (pspec->value_type)); GEnumValue* enum_value = g_enum_get_value (enum_class, G_PARAM_SPEC_ENUM (pspec)->default_value); str = sokoke_key_file_get_string_default (key_file, "settings", property, enum_value->value_name, NULL); enum_value = g_enum_get_value_by_name (enum_class, str); if (enum_value) g_object_set (settings, property, enum_value->value, NULL); else g_warning (_("Value '%s' is invalid for %s"), str, property); g_free (str); g_type_class_unref (enum_class); } else g_warning (_("Invalid configuration value '%s'"), property); } return settings; } static gboolean settings_save_to_file (MidoriWebSettings* settings, const gchar* filename, GError** error) { GKeyFile* key_file; GObjectClass* class; guint i, n_properties; GParamSpec** pspecs; GParamSpec* pspec; GType type; const gchar* property; gboolean saved; key_file = g_key_file_new (); class = G_OBJECT_GET_CLASS (settings); pspecs = g_object_class_list_properties (class, &n_properties); for (i = 0; i < n_properties; i++) { pspec = pspecs[i]; type = G_PARAM_SPEC_TYPE (pspec); property = g_param_spec_get_name (pspec); if (!(pspec->flags & G_PARAM_WRITABLE)) { gchar* prop_comment = g_strdup_printf ("# %s", property); g_key_file_set_string (key_file, "settings", prop_comment, ""); g_free (prop_comment); continue; } if (type == G_TYPE_PARAM_STRING) { const gchar* string; g_object_get (settings, property, &string, NULL); g_key_file_set_string (key_file, "settings", property, string ? string : ""); } else if (type == G_TYPE_PARAM_INT) { gint integer; g_object_get (settings, property, &integer, NULL); g_key_file_set_integer (key_file, "settings", property, integer); } else if (type == G_TYPE_PARAM_FLOAT) { gfloat number; g_object_get (settings, property, &number, NULL); g_key_file_set_double (key_file, "settings", property, number); } else if (type == G_TYPE_PARAM_BOOLEAN) { gboolean boolean; g_object_get (settings, property, &boolean, NULL); g_key_file_set_boolean (key_file, "settings", property, boolean); } else if (type == G_TYPE_PARAM_ENUM) { GEnumClass* enum_class = G_ENUM_CLASS ( g_type_class_ref (pspec->value_type)); gint integer; g_object_get (settings, property, &integer, NULL); GEnumValue* enum_value = g_enum_get_value (enum_class, integer); g_key_file_set_string (key_file, "settings", property, enum_value->value_name); } else g_warning (_("Invalid configuration value '%s'"), property); } saved = sokoke_key_file_save_to_file (key_file, filename, error); g_key_file_free (key_file); return saved; } static KatzeArray* search_engines_new_from_file (const gchar* filename, GError** error) { KatzeArray* search_engines; GKeyFile* key_file; gchar** engines; guint i, j, n_properties; KatzeItem* item; GParamSpec** pspecs; const gchar* property; gchar* value; search_engines = katze_array_new (KATZE_TYPE_ITEM); key_file = g_key_file_new (); g_key_file_load_from_file (key_file, filename, G_KEY_FILE_KEEP_COMMENTS, error); /*g_key_file_load_from_data_dirs(keyFile, sFilename, NULL , G_KEY_FILE_KEEP_COMMENTS, error);*/ engines = g_key_file_get_groups (key_file, NULL); pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (search_engines), &n_properties); for (i = 0; engines[i] != NULL; i++) { item = katze_item_new (); for (j = 0; j < n_properties; j++) { if (!G_IS_PARAM_SPEC_STRING (pspecs[j])) continue; property = g_param_spec_get_name (pspecs[j]); value = g_key_file_get_string (key_file, engines[i], property, NULL); g_object_set (item, property, value, NULL); g_free (value); } katze_array_add_item (search_engines, item); } g_strfreev (engines); g_key_file_free (key_file); return search_engines; } static gboolean search_engines_save_to_file (KatzeArray* search_engines, const gchar* filename, GError** error) { GKeyFile* key_file; guint n, i, j, n_properties; KatzeItem* item; const gchar* name; GParamSpec** pspecs; const gchar* property; gchar* value; gboolean saved; key_file = g_key_file_new (); n = katze_array_get_length (search_engines); pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (search_engines), &n_properties); for (i = 0; i < n; i++) { item = katze_array_get_nth_item (search_engines, i); name = katze_item_get_name (item); for (j = 0; j < n_properties; j++) { if (!G_IS_PARAM_SPEC_STRING (pspecs[j])) continue; property = g_param_spec_get_name (pspecs[j]); g_object_get (item, property, &value, NULL); if (value) g_key_file_set_string (key_file, name, property, value); g_free (value); } } saved = sokoke_key_file_save_to_file (key_file, filename, error); g_key_file_free (key_file); return saved; } #ifdef HAVE_LIBXML static KatzeItem* katze_item_from_xmlNodePtr (xmlNodePtr cur) { KatzeItem* item; xmlChar* key; item = katze_item_new (); key = xmlGetProp (cur, (xmlChar*)"href"); katze_item_set_uri (item, (gchar*)key); g_free (key); cur = cur->xmlChildrenNode; while (cur) { if (!xmlStrcmp (cur->name, (const xmlChar*)"title")) { key = xmlNodeGetContent (cur); katze_item_set_name (item, g_strstrip ((gchar*)key)); g_free (key); } else if (!xmlStrcmp (cur->name, (const xmlChar*)"desc")) { key = xmlNodeGetContent (cur); katze_item_set_text (item, g_strstrip ((gchar*)key)); g_free (key); } cur = cur->next; } return item; } /* Create an array from an xmlNodePtr */ static KatzeArray* katze_array_from_xmlNodePtr (xmlNodePtr cur) { KatzeArray* array; xmlChar* key; KatzeItem* item; array = katze_array_new (KATZE_TYPE_ARRAY); key = xmlGetProp (cur, (xmlChar*)"folded"); if (key) { /* if (!g_ascii_strncasecmp ((gchar*)key, "yes", 3)) folder->folded = TRUE; else if (!g_ascii_strncasecmp ((gchar*)key, "no", 2)) folder->folded = FALSE; else g_warning ("XBEL: Unknown value for folded."); */ xmlFree (key); } cur = cur->xmlChildrenNode; while (cur) { if (!xmlStrcmp (cur->name, (const xmlChar*)"title")) { key = xmlNodeGetContent (cur); katze_item_set_name (KATZE_ITEM (array), g_strstrip ((gchar*)key)); } else if (!xmlStrcmp (cur->name, (const xmlChar*)"desc")) { key = xmlNodeGetContent (cur); katze_item_set_text (KATZE_ITEM (array), g_strstrip ((gchar*)key)); } else if (!xmlStrcmp (cur->name, (const xmlChar*)"folder")) { item = (KatzeItem*)katze_array_from_xmlNodePtr (cur); katze_array_add_item (array, item); } else if (!xmlStrcmp (cur->name, (const xmlChar*)"bookmark")) { item = katze_item_from_xmlNodePtr (cur); katze_array_add_item (array, item); } else if (!xmlStrcmp (cur->name, (const xmlChar*)"separator")) { item = katze_item_new (); katze_array_add_item (array, item); } cur = cur->next; } return array; } /* Loads the contents from an xmlNodePtr into an array. */ static gboolean katze_array_from_xmlDocPtr (KatzeArray* array, xmlDocPtr doc) { xmlNodePtr cur; xmlChar* version; gchar* value; KatzeItem* item; cur = xmlDocGetRootElement (doc); version = xmlGetProp (cur, (xmlChar*)"version"); if (xmlStrcmp (version, (xmlChar*)"1.0")) g_warning ("XBEL version is not 1.0."); xmlFree (version); value = (gchar*)xmlGetProp (cur, (xmlChar*)"title"); katze_item_set_name (KATZE_ITEM (array), value); g_free (value); value = (gchar*)xmlGetProp (cur, (xmlChar*)"desc"); katze_item_set_text (KATZE_ITEM (array), value); g_free (value); if ((cur = xmlDocGetRootElement (doc)) == NULL) { /* Empty document */ return FALSE; } if (xmlStrcmp (cur->name, (const xmlChar*)"xbel")) { /* Wrong document kind */ return FALSE; } cur = cur->xmlChildrenNode; while (cur) { item = NULL; if (!xmlStrcmp (cur->name, (const xmlChar*)"folder")) item = (KatzeItem*)katze_array_from_xmlNodePtr (cur); else if (!xmlStrcmp (cur->name, (const xmlChar*)"bookmark")) item = katze_item_from_xmlNodePtr (cur); else if (!xmlStrcmp (cur->name, (const xmlChar*)"separator")) item = katze_item_new (); /*else if (!xmlStrcmp (cur->name, (const xmlChar*)"info")) item = katze_xbel_parse_info (xbel, cur);*/ if (item) katze_array_add_item (array, item); cur = cur->next; } return TRUE; } static gboolean katze_array_from_file (KatzeArray* array, const gchar* filename, GError** error) { xmlDocPtr doc; g_return_val_if_fail (katze_array_is_a (array, KATZE_TYPE_ITEM), FALSE); g_return_val_if_fail (filename != NULL, FALSE); if (!g_file_test (filename, G_FILE_TEST_EXISTS)) { /* File doesn't exist */ *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, _("File not found.")); return FALSE; } if ((doc = xmlParseFile (filename)) == NULL) { /* No valid xml or broken encoding */ *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Malformed document.")); return FALSE; } if (!katze_array_from_xmlDocPtr (array, doc)) { /* Parsing failed */ xmlFreeDoc (doc); *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_FAILED, _("Malformed document.")); return FALSE; } xmlFreeDoc (doc); return TRUE; } #endif #ifdef HAVE_SQLITE /* Open database 'dbname' */ static sqlite3* db_open (const char* dbname, GError** error) { sqlite3* db; if (sqlite3_open (dbname, &db)) { if (error) { *error = g_error_new (MIDORI_HISTORY_ERROR, MIDORI_HISTORY_ERROR_DB_OPEN, _("Failed to open database: %s\n"), sqlite3_errmsg (db)); } sqlite3_close (db); return NULL; } return (db); } /* Close database 'db' */ static void db_close (sqlite3* db) { sqlite3_close (db); } /* Execute an SQL statement and run 'callback' on the result data */ static gboolean db_exec_callback (sqlite3* db, const char* sqlcmd, int (*callback)(void*, int, char**, char**), void* cbarg, GError** error) { char* errmsg; if (sqlite3_exec (db, sqlcmd, callback, cbarg, &errmsg) != SQLITE_OK) { if (error) { *error = g_error_new (MIDORI_HISTORY_ERROR, MIDORI_HISTORY_ERROR_EXEC_SQL, _("Failed to execute database statement: %s\n"), errmsg); } sqlite3_free (errmsg); return FALSE; } return TRUE; } /* Execute a SQL statement */ static gboolean db_exec (sqlite3* db, const char* sqlcmd, GError** error) { return (db_exec_callback (db, sqlcmd, NULL, NULL, error)); } /* sqlite method for retrieving the date/ time */ static int gettimestr (void* data, int argc, char** argv, char** colname) { KatzeItem* item = KATZE_ITEM (data); (void) colname; g_return_val_if_fail (argc == 1, 1); katze_item_set_added (item, g_ascii_strtoull (argv[0], NULL, 10)); return 0; } static void midori_history_remove_item_cb (KatzeArray* history, KatzeItem* item, sqlite3* db) { gchar* sqlcmd; gboolean success = TRUE; GError* error = NULL; g_return_if_fail (KATZE_IS_ITEM (item)); sqlcmd = sqlite3_mprintf ( "DELETE FROM history WHERE uri = '%q' AND" " title = '%q' AND date = %" G_GINT64_FORMAT, katze_item_get_uri (item), katze_item_get_name (item), katze_item_get_added (item)); success = db_exec (db, sqlcmd, &error); if (!success) { g_printerr (_("Failed to remove history item: %s\n"), error->message); g_error_free (error); return ; } sqlite3_free (sqlcmd); } static void midori_history_clear_before_cb (KatzeArray* item, sqlite3* db) { g_signal_handlers_block_by_func (item, midori_history_remove_item_cb, db); } static void midori_history_clear_cb (KatzeArray* history, sqlite3* db) { GError* error = NULL; g_return_if_fail (KATZE_IS_ARRAY (history)); if (!db_exec (db, "DELETE FROM history", &error)) { g_printerr (_("Failed to clear history: %s\n"), error->message); g_error_free (error); } } static void midori_history_add_item_cb (KatzeArray* array, KatzeItem* item, sqlite3* db) { gchar* sqlcmd; gboolean success = TRUE; GError* error = NULL; g_return_if_fail (KATZE_IS_ITEM (item)); if (KATZE_IS_ARRAY (item)) { g_signal_connect_after (item, "add-item", G_CALLBACK (midori_history_add_item_cb), db); g_signal_connect (item, "remove-item", G_CALLBACK (midori_history_remove_item_cb), db); g_signal_connect (item, "clear", G_CALLBACK (midori_history_clear_before_cb), db); return; } /* New item, set added to the current date/ time */ if (!katze_item_get_added (item)) { if (!db_exec_callback (db, "SELECT date('now')", gettimestr, item, &error)) { g_printerr (_("Failed to add history item: %s\n"), error->message); g_error_free (error); return; } } sqlcmd = sqlite3_mprintf ("INSERT INTO history VALUES" "('%q', '%q', %" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT ")", katze_item_get_uri (item), katze_item_get_name (item), katze_item_get_added (item), katze_item_get_added (KATZE_ITEM (array))); success = db_exec (db, sqlcmd, &error); sqlite3_free (sqlcmd); if (!success) { g_printerr (_("Failed to add history item: %s\n"), error->message); g_error_free (error); return ; } } static int midori_history_add_items (void* data, int argc, char** argv, char** colname) { KatzeItem* item; KatzeArray* parent; KatzeArray* array; gint64 date; gint64 day; gint i; gint j; gint n; gint ncols = 4; gchar token[50]; array = KATZE_ARRAY (data); g_return_val_if_fail (KATZE_IS_ARRAY (array), 1); /* Test whether have the right number of columns */ g_return_val_if_fail (argc % ncols == 0, 1); for (i = 0; i < (argc - ncols) + 1; i++) { if (argv[i]) { if (colname[i] && !g_ascii_strcasecmp (colname[i], "uri") && colname[i + 1] && !g_ascii_strcasecmp (colname[i + 1], "title") && colname[i + 2] && !g_ascii_strcasecmp (colname[i + 2], "date") && colname[i + 3] && !g_ascii_strcasecmp (colname[i + 3], "day")) { item = katze_item_new (); katze_item_set_uri (item, argv[i]); katze_item_set_name (item, argv[i + 1]); date = g_ascii_strtoull (argv[i + 2], NULL, 10); day = g_ascii_strtoull (argv[i + 3], NULL, 10); katze_item_set_added (item, date); n = katze_array_get_length (array); for (j = n - 1; j >= 0; j--) { parent = katze_array_get_nth_item (array, j); if (day == katze_item_get_added (KATZE_ITEM (parent))) break; } if (j < 0) { parent = katze_array_new (KATZE_TYPE_ARRAY); katze_item_set_added (KATZE_ITEM (parent), day); strftime (token, sizeof (token), "%Y-%m-%d", localtime ((time_t *)&date)); katze_item_set_token (KATZE_ITEM (parent), token); katze_array_add_item (array, parent); } katze_array_add_item (parent, item); } } } return 0; } static int midori_history_test_day_column (void* data, int argc, char** argv, char** colname) { gint i; gboolean* has_day; has_day = (gboolean*)data; for (i = 0; i < argc; i++) { if (argv[i] && !g_ascii_strcasecmp (colname[i], "name") && !g_ascii_strcasecmp (argv[i], "day")) { *has_day = TRUE; break; } } return 0; } static sqlite3* midori_history_initialize (KatzeArray* array, const gchar* filename, GError** error) { sqlite3* db; KatzeItem* item; gint i, n; gboolean has_day; has_day = FALSE; if ((db = db_open (filename, error)) == NULL) return db; if (!db_exec (db, "CREATE TABLE IF NOT EXISTS " "history(uri text, title text, date integer, day integer)", error)) return NULL; if (!db_exec_callback (db, "PRAGMA table_info(history)", midori_history_test_day_column, &has_day, error)) return NULL; if (!has_day) { if (!db_exec (db, "BEGIN TRANSACTION;" "CREATE TEMPORARY TABLE backup (uri text, title text, date integer);" "INSERT INTO backup SELECT uri,title,date FROM history;" "DROP TABLE history;" "CREATE TABLE history (uri text, title text, date integer, day integer);" "INSERT INTO history SELECT uri,title,date," "julianday(date(date,'unixepoch','start of day','+1 day'))" " - julianday('0001-01-01','start of day')" "FROM backup;" "DROP TABLE backup;" "COMMIT;", error)) return NULL; } if (!db_exec_callback (db, "SELECT uri, title, date, day FROM history " "ORDER BY date ASC", midori_history_add_items, array, error)) return NULL; n = katze_array_get_length (array); for (i = 0; i < n; i++) { item = katze_array_get_nth_item (array, i); g_signal_connect_after (item, "add-item", G_CALLBACK (midori_history_add_item_cb), db); g_signal_connect (item, "remove-item", G_CALLBACK (midori_history_remove_item_cb), db); g_signal_connect (item, "clear", G_CALLBACK (midori_history_clear_before_cb), db); } return db; } static void midori_history_terminate (sqlite3* db, gint max_history_age) { gchar* sqlcmd; gboolean success = TRUE; GError* error = NULL; sqlcmd = g_strdup_printf ( "DELETE FROM history WHERE " "(julianday(date('now')) - julianday(date(date,'unixepoch')))" " >= %d", max_history_age); db_exec (db, sqlcmd, &error); if (!success) { /* i18n: Couldn't remove items that are older than n days */ g_printerr (_("Failed to remove old history items: %s\n"), error->message); g_error_free (error); return ; } g_free (sqlcmd); db_close (db); } #endif static void midori_app_quit_cb (MidoriApp* app) { gchar* config_file = build_config_filename ("running"); g_unlink (config_file); g_free (config_file); } static void settings_notify_cb (MidoriWebSettings* settings, GParamSpec* pspec) { gchar* config_file; GError* error; config_file = build_config_filename ("config"); error = NULL; if (!settings_save_to_file (settings, config_file, &error)) { g_warning (_("The configuration couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static void accel_map_changed_cb (GtkAccelMap* accel_map, gchar* accel_path, guint accel_key, GdkModifierType accel_mods) { gchar* config_file = build_config_filename ("accels"); gtk_accel_map_save (config_file); g_free (config_file); } static void midori_search_engines_add_item_cb (KatzeArray* search_engines, GObject* item) { gchar* config_file; GError* error; config_file = build_config_filename ("search"); error = NULL; if (!search_engines_save_to_file (search_engines, config_file, &error)) { g_warning (_("The search engines couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static void midori_search_engines_remove_item_cb (KatzeArray* search_engines, GObject* item) { gchar* config_file; GError* error; config_file = build_config_filename ("search"); error = NULL; if (!search_engines_save_to_file (search_engines, config_file, &error)) { g_warning (_("The search engines couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static gchar* _simple_xml_element (const gchar* name, const gchar* value) { gchar* value_escaped; gchar* markup; if (!value) return g_strdup (""); value_escaped = g_markup_escape_text (value, -1); markup = g_strdup_printf ("<%s>%s\n", name, value_escaped, name); g_free (value_escaped); return markup; } static gchar* katze_item_to_data (KatzeItem* item) { gchar* markup; g_return_val_if_fail (KATZE_IS_ITEM (item), NULL); markup = NULL; if (KATZE_IS_ARRAY (item)) { GString* _markup = g_string_new (NULL); guint n = katze_array_get_length (KATZE_ARRAY (item)); guint i; for (i = 0; i < n; i++) { KatzeItem* _item = katze_array_get_nth_item (KATZE_ARRAY (item), i); gchar* item_markup = katze_item_to_data (_item); g_string_append (_markup, item_markup); g_free (item_markup); } /* gchar* folded = item->folded ? NULL : g_strdup_printf (" folded=\"no\""); */ gchar* title = _simple_xml_element ("title", katze_item_get_name (item)); gchar* desc = _simple_xml_element ("desc", katze_item_get_text (item)); markup = g_strdup_printf ("\n%s%s%s\n", "" /* folded ? folded : "" */, title, desc, g_string_free (_markup, FALSE)); /* g_free (folded); */ g_free (title); g_free (desc); } else if (katze_item_get_uri (item)) { gchar* href_escaped = g_markup_escape_text (katze_item_get_uri (item), -1); gchar* href = g_strdup_printf (" href=\"%s\"", href_escaped); g_free (href_escaped); gchar* title = _simple_xml_element ("title", katze_item_get_name (item)); gchar* desc = _simple_xml_element ("desc", katze_item_get_text (item)); markup = g_strdup_printf ("\n%s%s%s\n", href, title, desc, ""); g_free (href); g_free (title); g_free (desc); } else markup = g_strdup ("\n"); return markup; } static gchar* katze_array_to_xml (KatzeArray* array, GError** error) { GString* inner_markup; guint i, n; KatzeItem* item; gchar* item_xml; gchar* title; gchar* desc; gchar* outer_markup; g_return_val_if_fail (katze_array_is_a (array, KATZE_TYPE_ITEM), NULL); inner_markup = g_string_new (NULL); n = katze_array_get_length (array); for (i = 0; i < n; i++) { item = katze_array_get_nth_item (array, i); item_xml = katze_item_to_data (item); g_string_append (inner_markup, item_xml); g_free (item_xml); } title = _simple_xml_element ("title", katze_item_get_name (KATZE_ITEM (array))); desc = _simple_xml_element ("desc", katze_item_get_text (KATZE_ITEM (array))); outer_markup = g_strdup_printf ( "%s%s\n%s%s%s\n", "\n", "\n", title, desc, g_string_free (inner_markup, FALSE)); g_free (title); g_free (desc); return outer_markup; } static gboolean katze_array_to_file (KatzeArray* array, const gchar* filename, GError** error) { gchar* data; FILE* fp; g_return_val_if_fail (katze_array_is_a (array, KATZE_TYPE_ITEM), FALSE); g_return_val_if_fail (filename, FALSE); if (!(data = katze_array_to_xml (array, error))) return FALSE; if (!(fp = fopen (filename, "w"))) { *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_ACCES, _("Writing failed.")); return FALSE; } fputs (data, fp); fclose (fp); g_free (data); return TRUE; } static void midori_bookmarks_notify_item_cb (KatzeArray* folder, GParamSpec* pspec, KatzeArray* bookmarks) { gchar* config_file; GError* error; config_file = build_config_filename ("bookmarks.xbel"); error = NULL; if (!katze_array_to_file (bookmarks, config_file, &error)) { g_warning (_("The bookmarks couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static void midori_bookmarks_add_item_cb (KatzeArray* folder, GObject* item, KatzeArray* bookmarks); static void midori_bookmarks_remove_item_cb (KatzeArray* bookmarks, GObject* item); static void midori_bookmarks_add_item_cb (KatzeArray* folder, GObject* item, KatzeArray* bookmarks) { gchar* config_file; GError* error; config_file = build_config_filename ("bookmarks.xbel"); error = NULL; if (!katze_array_to_file (bookmarks, config_file, &error)) { g_warning (_("The bookmarks couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); if (folder == bookmarks && KATZE_IS_ARRAY (item)) { g_signal_connect_after (item, "add-item", G_CALLBACK (midori_bookmarks_add_item_cb), bookmarks); g_signal_connect_after (item, "remove-item", G_CALLBACK (midori_bookmarks_remove_item_cb), NULL); } g_signal_connect_after (item, "notify", G_CALLBACK (midori_bookmarks_notify_item_cb), NULL); } static void midori_bookmarks_remove_item_cb (KatzeArray* bookmarks, GObject* item) { gchar* config_file; GError* error; config_file = build_config_filename ("bookmarks.xbel"); error = NULL; if (!katze_array_to_file (bookmarks, config_file, &error)) { g_warning (_("The bookmarks couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); if (KATZE_IS_ARRAY (item)) g_signal_handlers_disconnect_by_func (item, midori_bookmarks_add_item_cb, bookmarks); } static void midori_trash_add_item_cb (KatzeArray* trash, GObject* item) { gchar* config_file; GError* error; guint n; GObject* obsolete_item; config_file = build_config_filename ("tabtrash.xbel"); error = NULL; if (!katze_array_to_file (trash, config_file, &error)) { /* i18n: Trash, or wastebin, containing closed tabs */ g_warning (_("The trash couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); n = katze_array_get_length (trash); if (n > 10) { obsolete_item = katze_array_get_nth_item (trash, 0); katze_array_remove_item (trash, obsolete_item); } } static void midori_trash_remove_item_cb (KatzeArray* trash, GObject* item) { gchar* config_file; GError* error; config_file = build_config_filename ("tabtrash.xbel"); error = NULL; if (!katze_array_to_file (trash, config_file, &error)) { g_warning (_("The trash couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static void midori_app_add_browser_cb (MidoriApp* app, MidoriBrowser* browser, KatzeNet* net) { GtkWidget* panel; GtkWidget* addon; panel = katze_object_get_object (browser, "panel"); /* Transfers */ #if 0 addon = midori_view_new (net); gtk_widget_show (addon); midori_panel_append_widget (MIDORI_PANEL (panel), addon, STOCK_TRANSFERS, _("Transfers"), NULL); #endif /* Console */ addon = g_object_new (MIDORI_TYPE_CONSOLE, "app", app, NULL); gtk_widget_show (addon); midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon)); /* Userscripts */ addon = midori_addons_new (MIDORI_ADDON_USER_SCRIPTS, GTK_WIDGET (browser)); gtk_widget_show (addon); midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon)); /* Userstyles */ addon = midori_addons_new (MIDORI_ADDON_USER_STYLES, GTK_WIDGET (browser)); gtk_widget_show (addon); midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon)); /* Plugins */ addon = g_object_new (MIDORI_TYPE_PLUGINS, "app", app, NULL); gtk_widget_show (addon); midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon)); /* Extensions */ addon = g_object_new (MIDORI_TYPE_EXTENSIONS, "app", app, NULL); gtk_widget_show (addon); midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (addon)); } static void midori_browser_session_cb (MidoriBrowser* browser, gpointer pspec, KatzeArray* session) { gchar* config_file; GError* error; config_file = build_config_filename ("session.xbel"); error = NULL; if (!katze_array_to_file (session, config_file, &error)) { g_warning (_("The session couldn't be saved. %s"), error->message); g_error_free (error); } g_free (config_file); } static void midori_browser_weak_notify_cb (MidoriBrowser* browser, KatzeArray* session) { g_object_disconnect (browser, "any-signal", G_CALLBACK (midori_browser_session_cb), session, NULL); } typedef void (*GObjectConstructed) (GObject*); #if HAVE_LIBSOUP_2_25_2 /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Mostly copied from libSoup 2.24, coding style adjusted */ static SoupCookie* parse_cookie (gchar* line, time_t now) { gchar** result; SoupCookie *cookie = NULL; gboolean http_only; time_t max_age; gchar* host/*, *is_domain*/, *path, *secure, *expires, *name, *value; if (g_str_has_prefix (line, "#HttpOnly_")) { http_only = TRUE; line += strlen ("#HttpOnly_"); } else if (*line == '#' || g_ascii_isspace (*line)) return cookie; else http_only = FALSE; result = g_strsplit (line, "\t", -1); if (g_strv_length (result) != 7) goto out; /* Check this first */ expires = result[4]; max_age = strtoul (expires, NULL, 10) - now; if (max_age <= 0) goto out; host = result[0]; /* is_domain = result[1]; */ path = result[2]; secure = result[3]; name = result[5]; value = result[6]; cookie = soup_cookie_new (name, value, host, path, max_age); if (strcmp (secure, "FALSE")) soup_cookie_set_secure (cookie, TRUE); if (http_only) soup_cookie_set_http_only (cookie, TRUE); out: g_strfreev (result); return cookie; } /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Mostly copied from libSoup 2.24, coding style adjusted */ static void parse_line (SoupCookieJar* jar, gchar* line, time_t now) { SoupCookie* cookie; if ((cookie = parse_cookie (line, now))) soup_cookie_jar_add_cookie (jar, cookie); } /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Mostly copied from libSoup 2.24, coding style adjusted */ static void cookie_jar_load (SoupCookieJar* jar, const gchar* filename) { char* contents = NULL; gchar* line; gchar* p; gsize length = 0; time_t now = time (NULL); if (!g_file_get_contents (filename, &contents, &length, NULL)) return; line = contents; for (p = contents; *p; p++) { /* \r\n comes out as an extra empty line and gets ignored */ if (*p == '\r' || *p == '\n') { *p = '\0'; parse_line (jar, line, now); line = p + 1; } } parse_line (jar, line, now); g_free (contents); } /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Copied from libSoup 2.24, coding style preserved */ static void write_cookie (FILE *out, SoupCookie *cookie) { fseek (out, 0, SEEK_END); fprintf (out, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n", cookie->http_only ? "#HttpOnly_" : "", cookie->domain, *cookie->domain == '.' ? "TRUE" : "FALSE", cookie->path, cookie->secure ? "TRUE" : "FALSE", (gulong)soup_date_to_time_t (cookie->expires), cookie->name, cookie->value); } /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Copied from libSoup 2.24, coding style preserved */ static void delete_cookie (const char *filename, SoupCookie *cookie) { char *contents = NULL, *line, *p; gsize length = 0; FILE *f; SoupCookie *c; time_t now = time (NULL); if (!g_file_get_contents (filename, &contents, &length, NULL)) return; f = fopen (filename, "w"); if (!f) { g_free (contents); return; } line = contents; for (p = contents; *p; p++) { /* \r\n comes out as an extra empty line and gets ignored */ if (*p == '\r' || *p == '\n') { *p = '\0'; c = parse_cookie (line, now); if (!c) continue; if (!soup_cookie_equal (cookie, c)) write_cookie (f, c); line = p + 1; soup_cookie_free (c); } } c = parse_cookie (line, now); if (c) { if (!soup_cookie_equal (cookie, c)) write_cookie (f, c); soup_cookie_free (c); } g_free (contents); fclose (f); } /* Cookie jar saving to Mozilla format Copyright (C) 2008 Xan Lopez Copyright (C) 2008 Dan Winship Mostly copied from libSoup 2.24, coding style adjusted */ static void cookie_jar_changed_cb (SoupCookieJar* jar, SoupCookie* old_cookie, SoupCookie* new_cookie, gchar* filename) { MidoriApp* app; MidoriWebSettings* settings; MidoriAcceptCookies accept_cookies; if (old_cookie) delete_cookie (filename, old_cookie); if (new_cookie) { FILE *out; app = g_type_get_qdata (SOUP_TYPE_COOKIE_JAR, g_quark_from_static_string ("midori-app")); settings = katze_object_get_object (G_OBJECT (app), "settings"); accept_cookies = katze_object_get_enum (settings, "accept-cookies"); if (accept_cookies == MIDORI_ACCEPT_COOKIES_NONE) { soup_cookie_jar_delete_cookie (jar, new_cookie); } else if (accept_cookies == MIDORI_ACCEPT_COOKIES_SESSION && new_cookie->expires) { soup_cookie_jar_delete_cookie (jar, new_cookie); } else if (new_cookie->expires) { gint age = katze_object_get_int (settings, "maximum-cookie-age"); soup_cookie_set_max_age (new_cookie, age * SOUP_COOKIE_MAX_AGE_ONE_DAY); if (!(out = fopen (filename, "a"))) return; write_cookie (out, new_cookie); if (fclose (out) != 0) return; } } } #endif #if HAVE_LIBSOUP /* The following code hooks up to any created cookie jar in order to load and save cookies. This is *not* a generally advisable technique but merely a preliminary workaround until WebKit exposes its network backend and we can pass our own jar. */ static GObjectConstructed old_jar_constructed_cb; static void cookie_jar_constructed_cb (GObject* object) { #if HAVE_LIBSOUP_2_25_2 gchar* config_file; SoupCookieJar* jar; #endif if (old_jar_constructed_cb) old_jar_constructed_cb (object); g_type_set_qdata (SOUP_TYPE_COOKIE_JAR, g_quark_from_static_string ("midori-has-jar"), (void*)1); #if HAVE_LIBSOUP_2_25_2 config_file = build_config_filename ("cookies.txt"); jar = SOUP_COOKIE_JAR (object); cookie_jar_load (jar, config_file); g_signal_connect_data (jar, "changed", G_CALLBACK (cookie_jar_changed_cb), config_file, (GClosureNotify)g_free, 0); #endif } static void authentication_dialog_response_cb (GtkWidget* dialog, gint response, SoupAuth* auth) { GtkWidget* username; GtkWidget* password; SoupSession* session; SoupMessage* msg; if (response == GTK_RESPONSE_OK) { username = g_object_get_data (G_OBJECT (dialog), "username"); password = g_object_get_data (G_OBJECT (dialog), "password"); soup_auth_authenticate (auth, gtk_entry_get_text (GTK_ENTRY (username)), gtk_entry_get_text (GTK_ENTRY (password))); } session = g_object_get_data (G_OBJECT (dialog), "session"); msg = g_object_get_data (G_OBJECT (dialog), "msg"); gtk_widget_destroy (dialog); if (g_object_get_data (G_OBJECT (msg), "paused")) soup_session_unpause_message (session, msg); g_object_unref (auth); } static void soup_session_authenticate_cb (SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, MidoriApp* app) { GtkWidget* dialog; GtkSizeGroup* sizegroup; GtkWidget* hbox; GtkWidget* image; GtkWidget* label; GtkWidget* align; GtkWidget* entry; /* We want to ask for authentication exactly once, so we enforce this with a tag. There might be a better way. */ if (!retrying && g_object_get_data (G_OBJECT (msg), "midori-session-tag")) return; if (soup_message_is_keepalive (msg)) { /* We use another tag to indicate whether a message is paused. There doesn't seem to be API in libSoup to find that out. */ soup_session_pause_message (session, msg); g_object_set_data (G_OBJECT (msg), "paused", (void*)1); } g_object_set_data (G_OBJECT (msg), "midori-session-tag", (void*)1); dialog = gtk_dialog_new_with_buttons (_("Authentication Required"), katze_object_get_object (app, "browser"), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_window_set_icon_name (GTK_WINDOW (dialog), GTK_STOCK_DIALOG_AUTHENTICATION); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), 5); gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 5); hbox = gtk_hbox_new (FALSE, 6); image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); label = gtk_label_new (_("A username and a password are required\n" "to open this location:")); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); label = gtk_label_new (soup_auth_get_host (auth)); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label); /* If the realm is merely the host, omit the realm label */ if (g_strcmp0 (soup_auth_get_host (auth), soup_auth_get_realm (auth))) { label = gtk_label_new (soup_auth_get_realm (auth)); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label); } sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); hbox = gtk_hbox_new (FALSE, 6); label = gtk_label_new (_("Username")); align = gtk_alignment_new (0, 0.5, 0, 0); gtk_container_add (GTK_CONTAINER (align), label); gtk_size_group_add_widget (sizegroup, align); gtk_box_pack_start (GTK_BOX (hbox), align, TRUE, TRUE, 0); entry = gtk_entry_new (); gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); g_object_set_data (G_OBJECT (dialog), "username", entry); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); hbox = gtk_hbox_new (FALSE, 6); label = gtk_label_new (_("Password")); align = gtk_alignment_new (0, 0.5, 0, 0); gtk_container_add (GTK_CONTAINER (align), label); gtk_size_group_add_widget (sizegroup, align); gtk_box_pack_start (GTK_BOX (hbox), align, TRUE, TRUE, 0); entry = gtk_entry_new_with_max_length (32); gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); g_object_set_data (G_OBJECT (dialog), "password", entry); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_widget_show_all (GTK_DIALOG (dialog)->vbox); g_object_set_data (G_OBJECT (dialog), "session", session); g_object_set_data (G_OBJECT (dialog), "msg", msg); g_signal_connect (dialog, "response", G_CALLBACK (authentication_dialog_response_cb), g_object_ref (auth)); gtk_widget_show (dialog); } static void soup_session_settings_notify_http_proxy_cb (MidoriWebSettings* settings, GParamSpec* pspec, SoupSession* session) { gchar* http_proxy; SoupURI* proxy_uri; http_proxy = katze_object_get_string (settings, "http-proxy"); /* soup_uri_new expects a non-NULL string */ proxy_uri = soup_uri_new (http_proxy ? http_proxy : ""); g_free (http_proxy); g_object_set (session, "proxy-uri", proxy_uri, NULL); } static void soup_session_settings_notify_ident_string_cb (MidoriWebSettings* settings, GParamSpec* pspec, SoupSession* session) { gchar* ident_string = katze_object_get_string (settings, "ident-string"); g_object_set (session, "user-agent", ident_string, NULL); g_free (ident_string); } /* The following code hooks up to any created soup session in order to modify preferences. This is *not* a generally advisable technique but merely a preliminary workaround until WebKit exposes its session. */ static GObjectConstructed old_session_constructed_cb; static void soup_session_constructed_cb (GObject* object) { MidoriApp* app; MidoriWebSettings* settings; SoupSession* session; if (old_session_constructed_cb) old_session_constructed_cb (object); app = g_type_get_qdata (SOUP_TYPE_SESSION, g_quark_from_static_string ("midori-app")); settings = katze_object_get_object (app, "settings"); session = SOUP_SESSION (object); soup_session_settings_notify_http_proxy_cb (settings, NULL, session); soup_session_settings_notify_ident_string_cb (settings, NULL, session); g_signal_connect (settings, "notify::http-proxy", G_CALLBACK (soup_session_settings_notify_http_proxy_cb), object); g_signal_connect (settings, "notify::ident-string", G_CALLBACK (soup_session_settings_notify_ident_string_cb), object); g_signal_connect (session, "authenticate", G_CALLBACK (soup_session_authenticate_cb), app); } #endif static void button_modify_preferences_clicked_cb (GtkWidget* button, MidoriWebSettings* settings) { GtkWidget* dialog = midori_preferences_new ( GTK_WINDOW (gtk_widget_get_toplevel (button)), settings); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_DELETE_EVENT) gtk_widget_destroy (dialog); } static void button_reset_session_clicked_cb (GtkWidget* button, KatzeArray* session) { katze_array_clear (session); gtk_widget_set_sensitive (button, FALSE); } static GtkWidget* midori_create_diagnostic_dialog (MidoriWebSettings* settings, KatzeArray* _session) { GtkWidget* dialog; GdkScreen* screen; GtkIconTheme* icon_theme; GtkWidget* box; GtkWidget* button; dialog = gtk_message_dialog_new ( NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Midori seems to have crashed after it was opened " "for the last time. If this happend repeatedly, " "try one of the following options to solve the problem.")); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE); gtk_window_set_title (GTK_WINDOW (dialog), g_get_application_name ()); screen = gtk_widget_get_screen (dialog); if (screen) { icon_theme = gtk_icon_theme_get_for_screen (screen); if (gtk_icon_theme_has_icon (icon_theme, "midori")) gtk_window_set_icon_name (GTK_WINDOW (dialog), "midori"); else gtk_window_set_icon_name (GTK_WINDOW (dialog), "web-browser"); } box = gtk_hbox_new (FALSE, 0); button = gtk_button_new_with_mnemonic (_("Modify _preferences")); g_signal_connect (button, "clicked", G_CALLBACK (button_modify_preferences_clicked_cb), settings); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 4); button = gtk_button_new_with_mnemonic (_("Reset the last _session")); g_signal_connect (button, "clicked", G_CALLBACK (button_reset_session_clicked_cb), _session); gtk_widget_set_sensitive (button, !katze_array_is_empty (_session)); gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 4); button = gtk_button_new_with_mnemonic (_("Disable all _extensions")); gtk_widget_set_sensitive (button, FALSE); /* FIXME: Disable all extensions */ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 4); gtk_widget_show_all (box); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), box); return dialog; } static gboolean midori_load_extensions (gpointer data) { MidoriApp* app = MIDORI_APP (data); KatzeArray* extensions; const gchar* filename; MidoriExtension* extension; guint n, i; /* Load extensions */ extensions = katze_array_new (MIDORI_TYPE_EXTENSION); if (g_module_supported ()) { /* FIXME: Read extensions from system data dirs */ gchar* extension_path; GDir* extension_dir; if (!(extension_path = g_strdup (g_getenv ("MIDORI_EXTENSION_PATH")))) extension_path = g_build_filename (LIBDIR, PACKAGE_NAME, NULL); extension_dir = g_dir_open (extension_path, 0, NULL); if (extension_dir != NULL) { while ((filename = g_dir_read_name (extension_dir))) { gchar* fullname; GModule* module; typedef MidoriExtension* (*extension_init_func)(void); extension_init_func extension_init; /* Ignore files which don't have the correct suffix */ if (!g_str_has_suffix (filename, G_MODULE_SUFFIX)) continue; fullname = g_build_filename (extension_path, filename, NULL); module = g_module_open (fullname, G_MODULE_BIND_LOCAL); g_free (fullname); if (module && g_module_symbol (module, "extension_init", (gpointer) &extension_init)) extension = extension_init (); else { extension = g_object_new (MIDORI_TYPE_EXTENSION, "name", filename, "description", g_module_error (), NULL); g_warning ("%s", g_module_error ()); } katze_array_add_item (extensions, extension); g_object_unref (extension); } g_dir_close (extension_dir); } g_free (extension_path); } g_object_set (app, "extensions", extensions, NULL); n = katze_array_get_length (extensions); for (i = 0; i < n; i++) { extension = katze_array_get_nth_item (extensions, i); g_signal_emit_by_name (extension, "activate", app); } return FALSE; } static gboolean midori_load_session (gpointer data) { KatzeArray* _session = KATZE_ARRAY (data); MidoriBrowser* browser; MidoriApp* app = katze_item_get_parent (KATZE_ITEM (_session)); KatzeArray* session; KatzeItem* item; guint n, i; browser = midori_app_create_browser (app); midori_app_add_browser (app, browser); gtk_widget_show (GTK_WIDGET (browser)); if (katze_array_is_empty (_session)) { MidoriWebSettings* settings = katze_object_get_object (app, "settings"); MidoriStartup load_on_startup; gchar* homepage; item = katze_item_new (); g_object_get (settings, "load-on-startup", &load_on_startup, NULL); if (load_on_startup == MIDORI_STARTUP_BLANK_PAGE) katze_item_set_uri (item, ""); else { g_object_get (settings, "homepage", &homepage, NULL); katze_item_set_uri (item, homepage); g_free (homepage); } katze_array_add_item (_session, item); } session = midori_browser_get_proxy_array (browser); n = katze_array_get_length (_session); for (i = 0; i < n; i++) { item = katze_array_get_nth_item (_session, i); midori_browser_add_item (browser, item); } /* FIXME: Switch to the last active page */ item = katze_array_get_nth_item (_session, 0); if (!strcmp (katze_item_get_uri (item), "")) midori_browser_activate_action (browser, "Location"); g_object_unref (_session); g_signal_connect_after (browser, "notify::uri", G_CALLBACK (midori_browser_session_cb), session); g_signal_connect_after (browser, "add-tab", G_CALLBACK (midori_browser_session_cb), session); g_signal_connect_after (browser, "remove-tab", G_CALLBACK (midori_browser_session_cb), session); g_object_weak_ref (G_OBJECT (session), (GWeakNotify)(midori_browser_weak_notify_cb), browser); return FALSE; } static gint midori_run_script (const gchar* filename) { if (!(filename)) { g_print ("%s - %s\n", _("Midori"), _("No filename specified")); return 1; } JSGlobalContextRef js_context; gchar* exception; gchar* script; GError* error = NULL; #ifdef WEBKIT_CHECK_VERSION #if WEBKIT_CHECK_VERSION (1, 0, 3) #define HAVE_JSCONTEXTGROUP 1 #endif #endif #if HAVE_JSCONTEXTGROUP js_context = JSGlobalContextCreateInGroup (NULL, NULL); #else js_context = JSGlobalContextCreate (NULL); #endif if (g_file_get_contents (filename, &script, NULL, &error)) { if (sokoke_js_script_eval (js_context, script, &exception)) exception = NULL; g_free (script); } else if (error) { exception = g_strdup (error->message); g_error_free (error); } else exception = g_strdup (_("An unknown error occured.")); JSGlobalContextRelease (js_context); if (!exception) return 0; g_print ("%s - Exception: %s\n", filename, exception); return 1; } int main (int argc, char** argv) { gboolean run; gboolean version; gchar** uris; MidoriApp* app; gboolean result; GError* error; GOptionEntry entries[] = { { "run", 'r', 0, G_OPTION_ARG_NONE, &run, N_("Run the specified filename as javascript"), NULL }, { "version", 'V', 0, G_OPTION_ARG_NONE, &version, N_("Display program version"), NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &uris, N_("Addresses"), NULL }, { NULL } }; GString* error_messages; MidoriWebSettings* settings; gchar* config_file; MidoriStartup load_on_startup; KatzeArray* search_engines; KatzeArray* bookmarks; KatzeArray* history; KatzeArray* _session; KatzeArray* trash; guint i; gchar* uri; KatzeItem* item; gchar* uri_ready; #if HAVE_LIBSOUP GObjectClass* webkit_class; KatzeNet* net; SoupSession* s_session; #endif #if HAVE_LIBSOUP_2_25_2 SoupCookieJar* jar; #endif #ifdef HAVE_SQLITE sqlite3* db; gint max_history_age; #endif #if ENABLE_NLS setlocale (LC_ALL, ""); if (g_getenv ("NLSPATH")) bindtextdomain (GETTEXT_PACKAGE, g_getenv ("NLSPATH")); else bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); #endif /* Parse cli options */ run = FALSE; version = FALSE; uris = NULL; error = NULL; if (!gtk_init_with_args (&argc, &argv, _("[Addresses]"), entries, GETTEXT_PACKAGE, &error)) { g_print ("%s - %s\n", _("Midori"), error->message); g_error_free (error); return 1; } /* libSoup uses threads, therefore if WebKit is built with libSoup or Midori is using it, we need to initialize threads. */ if (!g_thread_supported ()) g_thread_init (NULL); stock_items_init (); g_set_application_name (_("Midori")); if (version) { g_print ( "%s %s\n\n" "Copyright (c) 2007-2008 Christian Dywan\n\n" "%s\n" "\t%s\n\n" "%s\n" "\thttp://www.twotoasts.de\n", _("Midori"), PACKAGE_VERSION, _("Please report comments, suggestions and bugs to:"), PACKAGE_BUGREPORT, _("Check for new versions at:") ); return 0; } /* Standalone javascript support */ if (run) return midori_run_script (uris ? *uris : NULL); app = midori_app_new (); /* FIXME: The app might be 'running' but actually showing a dialog after a crash, so running a new window isn't a good idea. */ if (midori_app_instance_is_running (app)) { GtkWidget* dialog; /* TODO: Open as many tabs as we have uris, seperated by pipes */ if (uris) result = midori_app_instance_send_uris (app, uris); else result = midori_app_instance_send_new_browser (app); if (result) return 0; dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("An instance of Midori is already running but not responding.\n")); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_DELETE_EVENT) gtk_widget_destroy (dialog); /* FIXME: Allow killing the existing instance */ return 1; } #if HAVE_LIBSOUP webkit_class = g_type_class_ref (WEBKIT_TYPE_WEB_VIEW); if (!g_object_class_find_property (webkit_class, "session")) { /* This is a nasty trick that allows us to manipulate cookies even without having a pointer to the jar. */ soup_cookie_jar_get_type (); SoupCookieJarClass* jar_class = g_type_class_ref (SOUP_TYPE_COOKIE_JAR); if (jar_class) { g_type_set_qdata (SOUP_TYPE_COOKIE_JAR, g_quark_from_static_string ("midori-app"), app); old_jar_constructed_cb = G_OBJECT_CLASS (jar_class)->constructed; G_OBJECT_CLASS (jar_class)->constructed = cookie_jar_constructed_cb; } /* This is a nasty trick that allows us to manipulate preferences even without having a pointer to the session. */ soup_session_get_type (); SoupSessionClass* session_class = g_type_class_ref (SOUP_TYPE_SESSION); if (session_class) { g_type_set_qdata (SOUP_TYPE_SESSION, g_quark_from_static_string ("midori-app"), app); old_session_constructed_cb = G_OBJECT_CLASS (session_class)->constructed; G_OBJECT_CLASS (session_class)->constructed = soup_session_constructed_cb; } } #endif /* Load configuration files */ error_messages = g_string_new (NULL); config_file = build_config_filename ("config"); error = NULL; settings = settings_new_from_file (config_file); katze_assign (config_file, build_config_filename ("accels")); gtk_accel_map_load (config_file); katze_assign (config_file, build_config_filename ("search")); error = NULL; search_engines = search_engines_new_from_file (config_file, &error); if (error) { /* FIXME: We may have a "file empty" error, how do we recognize that? if (error->code != G_FILE_ERROR_NOENT) g_string_append_printf (error_messages, _("The search engines couldn't be loaded. %s\n"), error->message); */ g_error_free (error); } bookmarks = katze_array_new (KATZE_TYPE_ARRAY); #ifdef HAVE_LIBXML katze_assign (config_file, build_config_filename ("bookmarks.xbel")); error = NULL; if (!katze_array_from_file (bookmarks, config_file, &error)) { if (error->code != G_FILE_ERROR_NOENT) g_string_append_printf (error_messages, _("The bookmarks couldn't be loaded: %s\n"), error->message); g_error_free (error); } #endif _session = katze_array_new (KATZE_TYPE_ITEM); #ifdef HAVE_LIBXML g_object_get (settings, "load-on-startup", &load_on_startup, NULL); if (load_on_startup == MIDORI_STARTUP_LAST_OPEN_PAGES) { katze_assign (config_file, build_config_filename ("session.xbel")); error = NULL; if (!katze_array_from_file (_session, config_file, &error)) { if (error->code != G_FILE_ERROR_NOENT) g_string_append_printf (error_messages, _("The session couldn't be loaded: %s\n"), error->message); g_error_free (error); } } #endif trash = katze_array_new (KATZE_TYPE_ITEM); #ifdef HAVE_LIBXML katze_assign (config_file, build_config_filename ("tabtrash.xbel")); error = NULL; if (!katze_array_from_file (trash, config_file, &error)) { if (error->code != G_FILE_ERROR_NOENT) g_string_append_printf (error_messages, _("The trash couldn't be loaded: %s\n"), error->message); g_error_free (error); } #endif #ifdef HAVE_SQLITE katze_assign (config_file, build_config_filename ("history.db")); #endif history = katze_array_new (KATZE_TYPE_ARRAY); #ifdef HAVE_SQLITE error = NULL; if ((db = midori_history_initialize (history, config_file, &error)) == NULL) { g_string_append_printf (error_messages, _("The history couldn't be loaded: %s\n"), error->message); g_error_free (error); } #endif /* In case of errors */ if (error_messages->len) { GdkScreen* screen; GtkIconTheme* icon_theme; GtkWidget* dialog = gtk_message_dialog_new ( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, _("The following errors occured:")); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE); gtk_window_set_title (GTK_WINDOW (dialog), g_get_application_name ()); screen = gtk_widget_get_screen (dialog); if (screen) { icon_theme = gtk_icon_theme_get_for_screen (screen); if (gtk_icon_theme_has_icon (icon_theme, "midori")) gtk_window_set_icon_name (GTK_WINDOW (dialog), "midori"); else gtk_window_set_icon_name (GTK_WINDOW (dialog), "web-browser"); } gtk_message_dialog_format_secondary_text ( GTK_MESSAGE_DIALOG (dialog), "%s", error_messages->str); gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, _("_Ignore"), GTK_RESPONSE_ACCEPT, NULL); if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT) { g_object_unref (settings); g_object_unref (search_engines); g_object_unref (bookmarks); g_object_unref (_session); g_object_unref (trash); g_object_unref (history); g_string_free (error_messages, TRUE); return 0; } gtk_widget_destroy (dialog); /* FIXME: Since we will overwrite files that could not be loaded , would we want to make backups? */ } g_string_free (error_messages, TRUE); #if HAVE_LIBSOUP webkit_class = g_type_class_ref (WEBKIT_TYPE_WEB_VIEW); if (g_object_class_find_property (webkit_class, "session")) { net = katze_net_new (); s_session = katze_net_get_session (net); #if HAVE_LIBSOUP_2_25_2 katze_assign (config_file, build_config_filename ("cookies.txt")); jar = soup_cookie_jar_text_new (config_file, FALSE); soup_session_add_feature (s_session, SOUP_SESSION_FEATURE (jar)); g_object_unref (jar); #endif soup_session_settings_notify_http_proxy_cb (settings, NULL, s_session); soup_session_settings_notify_ident_string_cb (settings, NULL, s_session); g_signal_connect (settings, "notify::http-proxy", G_CALLBACK (soup_session_settings_notify_http_proxy_cb), s_session); g_signal_connect (settings, "notify::ident-string", G_CALLBACK (soup_session_settings_notify_ident_string_cb), s_session); g_signal_connect (s_session, "authenticate", G_CALLBACK (soup_session_authenticate_cb), app); g_object_unref (net); } #endif /* Open as many tabs as we have uris, seperated by pipes */ i = 0; while (uris && uris[i]) { uri = strtok (g_strdup (uris[i]), "|"); while (uri != NULL) { item = katze_item_new (); uri_ready = sokoke_magic_uri (uri, NULL); katze_item_set_uri (item, uri_ready); g_free (uri_ready); katze_array_add_item (_session, item); uri = strtok (NULL, "|"); } g_free (uri); i++; } g_signal_connect_after (settings, "notify", G_CALLBACK (settings_notify_cb), NULL); g_signal_connect_after (gtk_accel_map_get (), "changed", G_CALLBACK (accel_map_changed_cb), NULL); g_signal_connect_after (search_engines, "add-item", G_CALLBACK (midori_search_engines_add_item_cb), NULL); g_signal_connect_after (search_engines, "remove-item", G_CALLBACK (midori_search_engines_remove_item_cb), NULL); g_signal_connect_after (bookmarks, "add-item", G_CALLBACK (midori_bookmarks_add_item_cb), bookmarks); g_signal_connect_after (bookmarks, "remove-item", G_CALLBACK (midori_bookmarks_remove_item_cb), NULL); if (!katze_array_is_empty (bookmarks)) { guint n; n = katze_array_get_length (bookmarks); for (i = 0; i < n; i++) { item = katze_array_get_nth_item (bookmarks, i); if (KATZE_IS_ARRAY (item)) { g_signal_connect_after (item, "add-item", G_CALLBACK (midori_bookmarks_add_item_cb), bookmarks); g_signal_connect_after (item, "remove-item", G_CALLBACK (midori_bookmarks_remove_item_cb), NULL); } g_signal_connect_after (item, "notify", G_CALLBACK (midori_bookmarks_notify_item_cb), bookmarks); } } g_signal_connect_after (trash, "add-item", G_CALLBACK (midori_trash_add_item_cb), NULL); g_signal_connect_after (trash, "remove-item", G_CALLBACK (midori_trash_remove_item_cb), NULL); #ifdef HAVE_SQLITE g_signal_connect_after (history, "add-item", G_CALLBACK (midori_history_add_item_cb), db); g_signal_connect_after (history, "clear", G_CALLBACK (midori_history_clear_cb), db); #endif /* We test for the presence of a dummy file which is created once and deleted during normal runtime, but persists in case of a crash. */ katze_assign (config_file, build_config_filename ("running")); if (katze_object_get_boolean (settings, "show-crash-dialog") && g_file_test (config_file, G_FILE_TEST_EXISTS)) { GtkWidget* dialog = midori_create_diagnostic_dialog (settings, _session); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); } else g_file_set_contents (config_file, "RUNNING", -1, NULL); g_signal_connect (app, "quit", G_CALLBACK (midori_app_quit_cb), NULL); g_object_set (app, "settings", settings, "bookmarks", bookmarks, "trash", trash, "search-engines", search_engines, "history", history, NULL); g_object_unref (history); g_object_unref (search_engines); g_object_unref (bookmarks); g_object_unref (trash); g_object_unref (settings); g_signal_connect (app, "add-browser", G_CALLBACK (midori_app_add_browser_cb), NULL); g_idle_add (midori_load_extensions, app); katze_item_set_parent (KATZE_ITEM (_session), app); g_idle_add (midori_load_session, _session); gtk_main (); #ifdef HAVE_SQLITE settings = katze_object_get_object (app, "settings"); g_object_get (settings, "maximum-history-age", &max_history_age, NULL); midori_history_terminate (db, max_history_age); #endif g_object_unref (app); g_free (config_file); return 0; }