From 7c0e58a8358f4097c699e90d03946579989744b7 Mon Sep 17 00:00:00 2001 From: Arno Renevier Date: Sun, 2 May 2010 11:53:05 +0200 Subject: [PATCH] Refactor userscripts and userstyles as an extension --- extensions/addons.c | 1262 ++++++++++++++++++++++++++++++++++++++++ midori/main.c | 6 - panels/midori-addons.c | 1079 ---------------------------------- panels/midori-addons.h | 67 --- 4 files changed, 1262 insertions(+), 1152 deletions(-) create mode 100644 extensions/addons.c delete mode 100644 panels/midori-addons.c delete mode 100644 panels/midori-addons.h diff --git a/extensions/addons.c b/extensions/addons.c new file mode 100644 index 00000000..2ccd4851 --- /dev/null +++ b/extensions/addons.c @@ -0,0 +1,1262 @@ +/* + Copyright (C) 2008 Christian Dywan + Copyright (C) 2008-2010 Arno Renevier + + 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. +*/ + +/* This extensions add support for user addons: userscripts and userstyles */ + +#include +#include +#include +#include + +#include "config.h" + +#if HAVE_UNISTD_H + #include +#endif + +#ifndef X_OK + #define X_OK 1 +#endif + +typedef enum +{ + ADDON_NONE, + ADDONS_USER_SCRIPTS, + ADDONS_USER_STYLES +} AddonsKind; + +#define ADDONS_TYPE \ + (addons_get_type ()) +#define ADDONS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), ADDONS_TYPE, Addons)) +#define ADDONS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), ADDONS_TYPE, AddonsClass)) +#define IS_ADDONS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ADDONS_TYPE)) +#define IS_ADDONS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), ADDONS_TYPE)) +#define ADDONS_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), ADDONS_TYPE, AddonsClass)) + +typedef struct _Addons Addons; +typedef struct _AddonsClass AddonsClass; + +struct _AddonsClass +{ + GtkVBoxClass parent_class; +}; + +struct _Addons +{ + GtkVBox parent_instance; + + GtkWidget* toolbar; + GtkWidget* treeview; + + AddonsKind kind; +}; + +static void +addons_iface_init (MidoriViewableIface* iface); + +G_DEFINE_TYPE_WITH_CODE (Addons, addons, GTK_TYPE_VBOX, + G_IMPLEMENT_INTERFACE (MIDORI_TYPE_VIEWABLE, + addons_iface_init)); + +struct AddonElement +{ + gchar* fullpath; + gchar* displayname; + gchar* description; + gchar* script_content; + + gboolean enabled; + gboolean broken; + + GSList* includes; + GSList* excludes; +}; + +struct AddonsList +{ + GtkListStore* liststore; + GSList* elements; +}; + +static void +midori_addons_button_add_clicked_cb (GtkToolItem* toolitem, + Addons* addons) +{ + gchar* addons_type; + gchar* path; + GtkWidget* dialog; + + if (addons->kind == ADDONS_USER_SCRIPTS) + { + addons_type = g_strdup ("userscripts"); + path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), + PACKAGE_NAME, "scripts", NULL); + } + else if (addons->kind == ADDONS_USER_STYLES) + { + addons_type = g_strdup ("userstyles"); + path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), + PACKAGE_NAME, "styles", NULL); + } + else + g_assert_not_reached (); + + dialog = gtk_message_dialog_new ( + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (addons))), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, + _("Copy %s to the folder %s."), addons_type, path); + g_free (addons_type); + g_free (path); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +GtkWidget* +addons_get_toolbar (MidoriViewable* viewable) +{ + GtkWidget* toolbar; + GtkToolItem* toolitem; + + g_return_val_if_fail (IS_ADDONS (viewable), NULL); + + if (!ADDONS (viewable)->toolbar) + { + toolbar = gtk_toolbar_new (); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ); + gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON); + toolitem = gtk_tool_item_new (); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); + gtk_widget_show (GTK_WIDGET (toolitem)); + + /* separator */ + toolitem = gtk_separator_tool_item_new (); + gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (toolitem), + FALSE); + gtk_tool_item_set_expand (toolitem, TRUE); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); + gtk_widget_show (GTK_WIDGET (toolitem)); + + /* add button */ + toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_ADD); + gtk_tool_item_set_is_important (toolitem, TRUE); + g_signal_connect (toolitem, "clicked", + G_CALLBACK (midori_addons_button_add_clicked_cb), viewable); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); + + gtk_widget_show (GTK_WIDGET (toolitem)); + ADDONS (viewable)->toolbar = toolbar; + + g_signal_connect (toolbar, "destroy", + G_CALLBACK (gtk_widget_destroyed), + &ADDONS (viewable)->toolbar); + } + + return ADDONS (viewable)->toolbar; +} + +static const gchar* +addons_get_label (MidoriViewable* viewable) +{ + Addons* addons = ADDONS (viewable); + if (addons->kind == ADDONS_USER_SCRIPTS) + return _("Userscripts"); + else if (addons->kind == ADDONS_USER_STYLES) + return _("Userstyles"); + return NULL; +} + +static const gchar* +addons_get_stock_id (MidoriViewable* viewable) +{ + Addons* addons = ADDONS (viewable); + if (addons->kind == ADDONS_USER_SCRIPTS) + return STOCK_SCRIPT; + else if (addons->kind == ADDONS_USER_STYLES) + return STOCK_STYLE; + return NULL; +} + +static void +addons_iface_init (MidoriViewableIface* iface) +{ + iface->get_stock_id = addons_get_stock_id; + iface->get_label = addons_get_label; + iface->get_toolbar = addons_get_toolbar; +} + +static void +addons_free_elements (GSList* elements) +{ + struct AddonElement* element; + GSList* start = elements; + + while (elements) + { + element = elements->data; + g_free (element->fullpath); + g_free (element->displayname); + g_free (element->description); + g_free (element->script_content); + g_slist_free (element->includes); + g_slist_free (element->excludes); + + elements = g_slist_next (elements); + } + + g_slist_free (start); +} + +static void +addons_class_init (AddonsClass* class) +{ +} + +static void +addons_treeview_render_tick_cb (GtkTreeViewColumn* column, + GtkCellRenderer* renderer, + GtkTreeModel* model, + GtkTreeIter* iter, + GtkWidget* treeview) +{ + struct AddonElement *element; + + gtk_tree_model_get (model, iter, 0, &element, -1); + + g_object_set (renderer, + "active", element->enabled, + "sensitive", !element->broken, + NULL); +} + +static void +addons_cell_renderer_toggled_cb (GtkCellRendererToggle* renderer, + const gchar* path, + Addons* addons) +{ + GtkTreeModel* model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (addons->treeview)); + if (gtk_tree_model_get_iter_from_string (model, &iter, path)) + { + struct AddonElement *element; + GtkTreePath* tree_path; + + gtk_tree_model_get (model, &iter, 0, &element, -1); + + element->enabled = !element->enabled; + + /* After enabling or disabling an element, the tree view + is not updated automatically; we need to notify tree model + in order to take the modification into account */ + tree_path = gtk_tree_path_new_from_string (path); + gtk_tree_model_row_changed (model, tree_path, &iter); + gtk_tree_path_free (tree_path); + } +} + +static void +addons_treeview_render_text_cb (GtkTreeViewColumn* column, + GtkCellRenderer* renderer, + GtkTreeModel* model, + GtkTreeIter* iter, + GtkWidget* treeview) +{ + struct AddonElement *element; + + gtk_tree_model_get (model, iter, 0, &element, -1); + + g_object_set (renderer, "text", element->displayname, NULL); + if (!element->enabled) + g_object_set (renderer, "sensitive", false, NULL); + else + g_object_set (renderer, "sensitive", true, NULL); +} + +static void +addons_treeview_row_activated_cb (GtkTreeView* treeview, + GtkTreePath* path, + GtkTreeViewColumn* column, + Addons* addons) +{ + GtkTreeModel* model = gtk_tree_view_get_model (treeview); + GtkTreeIter iter; + if (gtk_tree_model_get_iter (model, &iter, path)) + { + struct AddonElement *element; + + gtk_tree_model_get (model, &iter, 0, &element, -1); + + element->enabled = !element->enabled; + + /* After enabling or disabling an element, the tree view + is not updated automatically; we need to notify tree model + in order to take the modification into account */ + gtk_tree_model_row_changed (model, path, &iter); + } +} + +static GSList* +addons_get_directories (AddonsKind kind) +{ + gchar* folder_name; + GSList* directories; + const char* const* datadirs; + gchar* path; + + g_assert (kind == ADDONS_USER_SCRIPTS || kind == ADDONS_USER_STYLES); + + directories = NULL; + if (kind == ADDONS_USER_SCRIPTS) + folder_name = g_strdup ("scripts"); + else if (kind == ADDONS_USER_STYLES) + folder_name = g_strdup ("styles"); + else + g_assert_not_reached (); + + path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), + PACKAGE_NAME, folder_name, NULL); + if (g_access (path, X_OK) == 0) + directories = g_slist_prepend (directories, path); + else + g_free (path); + + datadirs = g_get_system_data_dirs (); + while (*datadirs) + { + path = g_build_path (G_DIR_SEPARATOR_S, *datadirs, + PACKAGE_NAME, folder_name, NULL); + if (g_access (path, X_OK) == 0) + directories = g_slist_prepend (directories, path); + else + g_free (path); + datadirs++; + } + + g_free (folder_name); + + return directories; +} + +static GSList* +addons_get_files (AddonsKind kind) +{ + GSList* files; + GDir* addon_dir; + GSList* list; + GSList* directories; + const gchar* filename; + gchar* dirname; + gchar* fullname; + gchar* file_extension; + + g_assert (kind == ADDONS_USER_SCRIPTS || kind == ADDONS_USER_STYLES); + + if (kind == ADDONS_USER_SCRIPTS) + file_extension = g_strdup (".js"); + else if (kind == ADDONS_USER_STYLES) + file_extension = g_strdup (".css"); + + files = NULL; + + directories = addons_get_directories (kind); + list = directories; + while (directories) + { + dirname = directories->data; + if ((addon_dir = g_dir_open (dirname, 0, NULL))) + { + while ((filename = g_dir_read_name (addon_dir))) + { + if (g_str_has_suffix (filename, file_extension)) + { + fullname = g_build_filename (dirname, filename, NULL); + files = g_slist_prepend (files, fullname); + } + } + g_dir_close (addon_dir); + } + g_free (dirname); + directories = g_slist_next (directories); + } + + g_slist_free (list); + g_free (file_extension); + + return files; +} + +static gboolean +js_metadata_from_file (const gchar* filename, + GSList** includes, + GSList** excludes, + gchar** name, + gchar** description) +{ + GIOChannel* channel; + gboolean found_meta; + gchar* line; + gchar* rest_of_line; + + if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) + return FALSE; + + channel = g_io_channel_new_file (filename, "r", 0); + if (!channel) + return FALSE; + + found_meta = FALSE; + + while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL) + == G_IO_STATUS_NORMAL) + { + if (g_str_has_prefix (line, "// ==UserScript==")) + found_meta = TRUE; + else if (found_meta) + { + if (g_str_has_prefix (line, "// ==/UserScript==")) + found_meta = FALSE; + else if (g_str_has_prefix (line, "// @require ") || + g_str_has_prefix (line, "// @resource ")) + { + /* We don't support these, so abort here */ + g_free (line); + g_io_channel_shutdown (channel, false, 0); + g_slist_free (*includes); + g_slist_free (*excludes); + *includes = NULL; + *excludes = NULL; + return FALSE; + } + else if (includes && g_str_has_prefix (line, "// @include ")) + { + rest_of_line = g_strdup (line + strlen ("// @include ")); + rest_of_line = g_strstrip (rest_of_line); + *includes = g_slist_prepend (*includes, rest_of_line); + } + else if (excludes && g_str_has_prefix (line, "// @exclude ")) + { + rest_of_line = g_strdup (line + strlen ("// @exclude ")); + rest_of_line = g_strstrip (rest_of_line); + *excludes = g_slist_prepend (*excludes, rest_of_line); + } + else if (name && g_str_has_prefix (line, "// @name ")) + { + rest_of_line = g_strdup (line + strlen ("// @name ")); + rest_of_line = g_strstrip (rest_of_line); + *name = rest_of_line; + } + else if (description && g_str_has_prefix (line, "// @description ")) + { + rest_of_line = g_strdup (line + strlen ("// @description ")); + rest_of_line = g_strstrip (rest_of_line); + *description = rest_of_line; + } + } + g_free (line); + } + g_io_channel_shutdown (channel, false, 0); + g_io_channel_unref (channel); + + return TRUE; +} + +static gboolean +css_metadata_from_file (const gchar* filename, + GSList** includes, + GSList** excludes) +{ + GIOChannel* channel; + gchar* line; + gchar* rest_of_line; + + if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) + return FALSE; + + channel = g_io_channel_new_file (filename, "r", 0); + if (!channel) + return FALSE; + + while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL) + == G_IO_STATUS_NORMAL) + { + if (g_str_has_prefix (line, "@-moz-document")) + { /* FIXME: We merely look for includes. We should honor blocks. */ + if (includes) + { + gchar** parts; + guint i; + + rest_of_line = g_strdup (line + strlen ("@-moz-document")); + rest_of_line = g_strstrip (rest_of_line); + parts = g_strsplit (rest_of_line, " ", 0); + i = 0; + while (parts[i]) + { + gchar* value = NULL; + if (g_str_has_prefix (parts[i], "url-prefix(")) + value = g_strdup (parts[i] + strlen ("url-prefix(")); + else if (g_str_has_prefix (parts[i], "url(")) + value = g_strdup (parts[i] + strlen ("url(")); + if (value) + { + guint j; + + if (value[0] != '\'' && value[0] != '"') + { + /* Wrong syntax, abort */ + g_free (value); + g_strfreev (parts); + g_free (line); + g_io_channel_shutdown (channel, false, 0); + g_slist_free (*includes); + g_slist_free (*excludes); + *includes = NULL; + *excludes = NULL; + return FALSE; + } + j = 1; + while (value[j] != '\0') + { + if (value[j] == value[0]) + break; + j++; + } + *includes = g_slist_prepend (*includes, g_strndup (value + 1, j - 1)); + g_free (value); + } + + /* FIXME: Recognize "domain" */ + i++; + } + g_strfreev (parts); + } + } + g_free (line); + } + g_io_channel_shutdown (channel, false, 0); + g_io_channel_unref (channel); + + return TRUE; +} + +static gboolean +addons_get_element_content (gchar* file_path, + AddonsKind kind, + gchar** content) +{ + gchar* file_content; + guint meta; + guint i, n; + + g_assert (kind == ADDONS_USER_SCRIPTS || kind == ADDONS_USER_STYLES); + + if (g_file_get_contents (file_path, &file_content, NULL, NULL)) + { + if (kind == ADDONS_USER_SCRIPTS) + { + *content = g_strdup_printf ( + "window.addEventListener ('DOMContentLoaded'," + "function () { %s }, true);", file_content); + } + else if (kind == ADDONS_USER_STYLES) + { + meta = 0; + n = strlen (file_content); + for (i = 0; i < n; i++) + { + /* Replace line breaks with spaces */ + if (file_content[i] == '\n' || file_content[i] == '\r') + file_content[i] = ' '; + /* Change all single quotes to double quotes */ + if (file_content[i] == '\'') + file_content[i] = '\"'; + /* Turn metadata we inspected earlier into comments */ + if (!meta && file_content[i] == '@') + { + file_content[i] = '/'; + meta++; + } + else if (meta == 1 + && (file_content[i] == '-' || file_content[i] == 'n')) + { + file_content[i] = '*'; + meta++; + } + else if (meta == 2 && file_content[i] == '{') + { + file_content[i - 1] = '*'; + file_content[i] = '/'; + meta++; + } + else if (meta == 3 && file_content[i] == '{') + meta++; + else if (meta == 4 && file_content[i] == '}') + meta--; + else if (meta == 3 && file_content[i] == '}') + { + file_content[i] = ' '; + meta = 0; + } + } + + *content = g_strdup_printf ( + "window.addEventListener ('DOMContentLoaded'," + "function () {" + "var mystyle = document.createElement(\"style\");" + "mystyle.setAttribute(\"type\", \"text/css\");" + "mystyle.appendChild(document.createTextNode('%s'));" + "var head = document.getElementsByTagName(\"head\")[0];" + "if (head) head.appendChild(mystyle);" + "else document.documentElement.insertBefore" + "(mystyle, document.documentElement.firstChild);" + "}, true);", + file_content); + + } + g_free (file_content); + if (*content) + return TRUE; + } + return FALSE; +} + +static void +addons_update_elements (MidoriExtension* extension, + AddonsKind kind) +{ + GSList* addon_files; + GSList* files_list; + gchar* name; + gchar* fullpath; + struct AddonElement* element; + struct AddonsList* addons_list; + GSList* elements = NULL; + GtkListStore* liststore = NULL; + GtkTreeIter iter; + gchar* config_file; + GKeyFile* keyfile; + + if (kind == ADDONS_USER_SCRIPTS) + addons_list = g_object_get_data (G_OBJECT (extension), "scripts-list"); + else if (kind == ADDONS_USER_STYLES) + addons_list = g_object_get_data (G_OBJECT (extension), "styles-list"); + else + g_assert_not_reached (); + + if (addons_list) + { + liststore = addons_list->liststore; + elements = addons_list->elements; + } + + if (elements) + addons_free_elements (elements); + + if (liststore) + gtk_list_store_clear (liststore); + else + liststore = gtk_list_store_new (3, G_TYPE_POINTER, + G_TYPE_INT, + G_TYPE_STRING); + + keyfile = g_key_file_new (); + config_file = g_build_filename (midori_extension_get_config_dir (extension), + "addons", NULL); + g_key_file_load_from_file (keyfile, config_file, G_KEY_FILE_NONE, NULL); + + addon_files = addons_get_files (kind); + files_list = addon_files; + + elements = NULL; + while (addon_files) + { + fullpath = addon_files->data; + element = g_new (struct AddonElement, 1); + element->displayname = g_filename_display_basename (fullpath); + element->fullpath = fullpath; + element->enabled = TRUE; + element->broken = FALSE; + element->includes = NULL; + element->excludes = NULL; + element->description = NULL; + element->script_content = NULL; + + if (kind == ADDONS_USER_SCRIPTS) + { + name = NULL; + if (!js_metadata_from_file (fullpath, + &element->includes, &element->excludes, + &name, &element->description)) + element->broken = TRUE; + + if (name) + katze_assign (element->displayname, name); + + if (!element->broken) + if (!addons_get_element_content (fullpath, kind, + &(element->script_content))) + element->broken = TRUE; + + if (g_key_file_get_integer (keyfile, "scripts", fullpath, NULL) & 1) + element->enabled = FALSE; + } + else if (kind == ADDONS_USER_STYLES) + { + if (!css_metadata_from_file (fullpath, + &element->includes, + &element->excludes)) + element->broken = TRUE; + + if (!element->broken) + if (!addons_get_element_content (fullpath, kind, + &(element->script_content))) + element->broken = TRUE; + + if (g_key_file_get_integer (keyfile, "styles", fullpath, NULL) & 1) + element->enabled = FALSE; + } + + gtk_list_store_append (liststore, &iter); + gtk_list_store_set (liststore, &iter, + 0, element, 1, 0, 2, "", -1); + + addon_files = g_slist_next (addon_files); + elements = g_slist_prepend (elements, element); + } + g_slist_free (files_list); + g_free (config_file); + g_key_file_free (keyfile); + + if (addons_list) + g_free (addons_list); + addons_list = g_new (struct AddonsList, 1); + addons_list->elements = elements; + addons_list->liststore = liststore; + + if (kind == ADDONS_USER_SCRIPTS) + g_object_set_data (G_OBJECT (extension), "scripts-list", addons_list); + else if (kind == ADDONS_USER_STYLES) + g_object_set_data (G_OBJECT (extension), "styles-list", addons_list); +} + +static void +addons_init (Addons* addons) +{ + GtkTreeViewColumn* column; + GtkCellRenderer* renderer_text; + GtkCellRenderer* renderer_toggle; + + addons->treeview = gtk_tree_view_new (); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (addons->treeview), FALSE); + column = gtk_tree_view_column_new (); + renderer_toggle = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, renderer_toggle, FALSE); + gtk_tree_view_column_set_cell_data_func (column, renderer_toggle, + (GtkTreeCellDataFunc)addons_treeview_render_tick_cb, + addons->treeview, NULL); + g_signal_connect (renderer_toggle, "toggled", + G_CALLBACK (addons_cell_renderer_toggled_cb), addons); + gtk_tree_view_append_column (GTK_TREE_VIEW (addons->treeview), column); + column = gtk_tree_view_column_new (); + renderer_text = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer_text, FALSE); + gtk_tree_view_column_set_cell_data_func (column, renderer_text, + (GtkTreeCellDataFunc)addons_treeview_render_text_cb, + addons->treeview, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (addons->treeview), column); + g_signal_connect (addons->treeview, "row-activated", + G_CALLBACK (addons_treeview_row_activated_cb), + addons); + gtk_widget_show (addons->treeview); + gtk_box_pack_start (GTK_BOX (addons), addons->treeview, TRUE, TRUE, 0); +} + +static gchar* +addons_convert_to_simple_regexp (const gchar* pattern) +{ + guint len; + gchar* dest; + guint pos; + guint i; + gchar c; + + len = strlen (pattern); + dest = g_malloc0 (len * 2 + 1); + dest[0] = '^'; + pos = 1; + + for (i = 0; i < len; i++) + { + c = pattern[i]; + switch (c) + { + case '*': + dest[pos] = '.'; + dest[pos + 1] = c; + pos++; + pos++; + break; + case '.' : + case '?' : + case '^' : + case '$' : + case '+' : + case '{' : + case '[' : + case '|' : + case '(' : + case ')' : + case ']' : + case '\\' : + dest[pos] = '\\'; + dest[pos + 1] = c; + pos++; + pos++; + break; + case ' ' : + break; + default: + dest[pos] = pattern[i]; + pos ++; + } + } + return dest; +} + +static gboolean +addons_may_run (const gchar* uri, + GSList** includes, + GSList** excludes) +{ + gboolean match; + GSList* list; + + if (*includes) + match = FALSE; + else + match = TRUE; + + list = *includes; + while (list) + { + gchar* re = addons_convert_to_simple_regexp (list->data); + gboolean matched = g_regex_match_simple (re, uri, 0, 0); + g_free (re); + if (matched) + { + match = TRUE; + break; + } + list = g_slist_next (list); + } + if (!match) + return FALSE; + + list = *excludes; + while (list) + { + gchar* re = addons_convert_to_simple_regexp (list->data); + gboolean matched = g_regex_match_simple (re, uri, 0, 0); + g_free (re); + if (matched) + { + match = FALSE; + break; + } + list = g_slist_next (list); + } + return match; +} + +static gboolean +addons_skip_element (struct AddonElement* element, + gchar* uri) +{ + if (!element->enabled || element->broken) + return TRUE; + if (element->includes || element->excludes) + if (!addons_may_run (uri, &element->includes, &element->excludes)) + return TRUE; + return FALSE; +} + +static void +addons_context_ready_cb (WebKitWebView* web_view, + WebKitWebFrame* web_frame, + JSContextRef js_context, + JSObjectRef js_window, + MidoriExtension* extension) +{ + gchar* uri; + GSList* scripts, *styles; + struct AddonElement* script, *style; + struct AddonsList* scripts_list, *styles_list; + uri = katze_object_get_string (web_view, "uri"); + /* Don't run scripts or styles on blank or special pages */ + if (!(uri && *uri && strncmp (uri, "about:", 6))) + { + g_free (uri); + return; + } + + scripts_list = g_object_get_data (G_OBJECT (extension), "scripts-list"); + scripts = scripts_list->elements; + while (scripts) + { + script = scripts->data; + if (addons_skip_element (script, uri)) + { + scripts = g_slist_next (scripts); + continue; + } + if (script->script_content) + webkit_web_view_execute_script (web_view, script->script_content); + scripts = g_slist_next (scripts); + } + + styles_list = g_object_get_data (G_OBJECT (extension), "styles-list"); + styles = styles_list->elements; + while (styles) + { + style = styles->data; + if (addons_skip_element (style, uri)) + { + styles = g_slist_next (styles); + continue; + } + if (style->script_content) + webkit_web_view_execute_script (web_view, style->script_content); + styles = g_slist_next (styles); + } +} + +static void +addons_add_tab_cb (MidoriBrowser* browser, + MidoriView* view, + MidoriExtension* extension) +{ + GtkWidget* web_view = midori_view_get_web_view (view); + g_signal_connect (web_view, "window-object-cleared", + G_CALLBACK (addons_context_ready_cb), extension); +} + +static void +addons_add_tab_foreach_cb (MidoriView* view, + MidoriBrowser* browser, + MidoriExtension* extension) +{ + addons_add_tab_cb (browser, view, extension); +} + +static void +addons_deactivate_tabs (MidoriView* view, + MidoriExtension* extension) +{ + GtkWidget* web_view = midori_view_get_web_view (view); + g_signal_handlers_disconnect_by_func ( + web_view, addons_context_ready_cb, extension); +} + +static void +addons_browser_destroy (MidoriBrowser* browser, + MidoriExtension* extension) +{ + GtkWidget* scripts, *styles; + + midori_browser_foreach (browser, (GtkCallback)addons_deactivate_tabs, extension); + g_signal_handlers_disconnect_by_func (browser, addons_add_tab_cb, extension); + + scripts = (GtkWidget*)g_object_get_data (G_OBJECT (browser), "scripts-addons"); + gtk_widget_destroy (scripts); + styles = (GtkWidget*)g_object_get_data (G_OBJECT (browser), "styles-addons"); + gtk_widget_destroy (styles); +} + +GtkWidget* +addons_new (AddonsKind kind, MidoriExtension* extension) +{ + GtkWidget* addons; + GtkListStore* liststore; + struct AddonsList* list; + + addons = g_object_new (ADDONS_TYPE, NULL); + ADDONS (addons)->kind = kind; + + if (kind == ADDONS_USER_SCRIPTS) + list = g_object_get_data (G_OBJECT (extension), "scripts-list"); + else if (kind == ADDONS_USER_STYLES) + list = g_object_get_data (G_OBJECT (extension), "styles-list"); + else + g_assert_not_reached (); + + liststore = list->liststore; + gtk_tree_view_set_model (GTK_TREE_VIEW (ADDONS(addons)->treeview), + GTK_TREE_MODEL (liststore)); + gtk_widget_queue_draw (GTK_WIDGET (ADDONS(addons)->treeview)); + + return addons; +} + +static void +addons_app_add_browser_cb (MidoriApp* app, + MidoriBrowser* browser, + MidoriExtension* extension) +{ + GtkWidget* panel; + GtkWidget* scripts, *styles; + + midori_browser_foreach (browser, + (GtkCallback)addons_add_tab_foreach_cb, extension); + g_signal_connect (browser, "add-tab", + G_CALLBACK (addons_add_tab_cb), extension); + panel = katze_object_get_object (browser, "panel"); + + scripts = addons_new (ADDONS_USER_SCRIPTS, extension); + gtk_widget_show (scripts); + midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (scripts)); + g_object_set_data (G_OBJECT (browser), "scripts-addons", scripts); + + styles = addons_new (ADDONS_USER_STYLES, extension); + gtk_widget_show (styles); + midori_panel_append_page (MIDORI_PANEL (panel), MIDORI_VIEWABLE (styles)); + g_object_set_data (G_OBJECT (browser), "styles-addons", styles); + + g_object_unref (panel); +} + +static void +addons_save_settings (MidoriApp* app, + MidoriExtension* extension) +{ + struct AddonsList* scripts_list, *styles_list; + struct AddonElement* script, *style; + GSList* scripts, *styles; + GKeyFile* keyfile; + const gchar* config_dir; + gchar* config_file; + GError* error = NULL; + + keyfile = g_key_file_new (); + + /* scripts */ + scripts_list = g_object_get_data (G_OBJECT (extension), "scripts-list"); + scripts = scripts_list->elements; + while (scripts) + { + script = scripts->data; + if (!script->enabled) + g_key_file_set_integer (keyfile, "scripts", script->fullpath, 1); + scripts = g_slist_next (scripts); + } + + /* styles */ + styles_list = g_object_get_data (G_OBJECT (extension), "styles-list"); + styles = styles_list->elements; + while (styles) + { + style = styles->data; + if (!style->enabled) + g_key_file_set_integer (keyfile, "styles", style->fullpath, 1); + styles = g_slist_next (styles); + } + + config_dir = midori_extension_get_config_dir (extension); + config_file = g_build_filename (config_dir, "addons", NULL); + katze_mkdir_with_parents (config_dir, 0700); + sokoke_key_file_save_to_file (keyfile, config_file, &error); + if (error) + { + g_warning (_("The configuration of the extension '%s' couldn't be saved: %s\n"), + _("User addons"), error->message); + g_error_free (error); + } + + g_free (config_file); + g_key_file_free (keyfile); +} + +static void +addons_disable_monitors (MidoriExtension* extension) +{ + GSList* monitors; + + monitors = g_object_get_data (G_OBJECT (extension), "monitors"); + if (!monitors) + return; + + g_slist_foreach (monitors, (GFunc)g_file_monitor_cancel, NULL); + g_slist_free (monitors); + g_object_set_data (G_OBJECT (extension), "monitors", NULL); +} + +static void +addons_deactivate_cb (MidoriExtension* extension, + MidoriApp* app) +{ + KatzeArray* browsers; + MidoriBrowser* browser; + GSource* source; + guint i = 0; + + addons_disable_monitors (extension); + addons_save_settings (NULL, extension); + + browsers = katze_object_get_object (app, "browsers"); + while ((browser = katze_array_get_nth_item (browsers, i++))) + addons_browser_destroy (browser, extension); + + source = g_object_get_data (G_OBJECT (extension), "monitor-timer"); + if (source && !g_source_is_destroyed (source)) + g_source_destroy (source); + + g_signal_handlers_disconnect_by_func ( + app, addons_app_add_browser_cb, extension); + g_signal_handlers_disconnect_by_func ( + app, addons_save_settings, extension); + g_signal_handlers_disconnect_by_func ( + extension, addons_deactivate_cb, app); + + g_object_unref (browsers); +} + +static gboolean +addons_reset_all_elements_cb (MidoriExtension* extension) +{ + addons_save_settings (NULL, extension); + addons_update_elements (extension, ADDONS_USER_STYLES); + addons_update_elements (extension, ADDONS_USER_SCRIPTS); + g_object_set_data (G_OBJECT (extension), "monitor-timer", NULL); + return FALSE; +} + +static void +addons_directory_monitor_changed (GFileMonitor* monitor, + GFile* child, + GFile* other_file, + GFileMonitorEvent flags, + MidoriExtension* extension) +{ + char* basename; + GSource* source; + + basename = g_file_get_basename (child); + if (g_str_has_prefix (basename, ".") || + g_str_has_suffix (basename, "~")) /* Hidden or temporary files */ + return; + + /* We receive a lot of change events, so we use a timeout to trigger + elements update only once */ + source = g_object_get_data (G_OBJECT (extension), "monitor-timer"); + if (source && !g_source_is_destroyed (source)) + g_source_destroy (source); + + source = g_timeout_source_new_seconds (1); + g_source_set_callback (source, (GSourceFunc)addons_reset_all_elements_cb, + extension, NULL); + g_source_attach (source, NULL); + g_object_set_data (G_OBJECT (extension), "monitor-timer", source); + g_source_unref (source); +} + +static void +addons_monitor_directories (MidoriExtension* extension, + AddonsKind kind) +{ + GSList* directories; + GSList* list; + GError* error; + GSList* monitors; + GFileMonitor* monitor; + GFile* directory; + + g_assert (kind == ADDONS_USER_SCRIPTS || kind == ADDONS_USER_STYLES); + + monitors = g_object_get_data (G_OBJECT (extension), "monitors"); + + directories = addons_get_directories (kind); + list = directories; + while (directories) + { + directory = g_file_new_for_path (directories->data); + directories = g_slist_next (directories); + error = NULL; + monitor = g_file_monitor_directory (directory, + G_FILE_MONITOR_NONE, + NULL, &error); + if (monitor) + { + g_signal_connect (monitor, "changed", + G_CALLBACK (addons_directory_monitor_changed), extension); + monitors = g_slist_prepend (monitors, monitor); + } + else + { + g_warning (_("Can't monitor folder '%s': %s"), + g_file_get_parse_name (directory), error->message); + g_error_free (error); + } + g_object_unref (directory); + } + g_object_set_data (G_OBJECT (extension), "monitors", monitors); + g_slist_free (directories); +} + +static void +addons_activate_cb (MidoriExtension* extension, + MidoriApp* app) +{ + KatzeArray* browsers; + MidoriBrowser* browser; + guint i; + + browsers = katze_object_get_object (app, "browsers"); + addons_update_elements (extension, ADDONS_USER_STYLES); + addons_monitor_directories (extension, ADDONS_USER_STYLES); + addons_update_elements (extension, ADDONS_USER_SCRIPTS); + addons_monitor_directories (extension, ADDONS_USER_STYLES); + + i = 0; + while ((browser = katze_array_get_nth_item (browsers, i++))) + addons_app_add_browser_cb (app, browser, extension); + g_object_unref (browsers); + + g_signal_connect (app, "add-browser", + G_CALLBACK (addons_app_add_browser_cb), extension); + + g_signal_connect (app, "quit", + G_CALLBACK (addons_save_settings), extension); + + g_signal_connect (extension, "deactivate", + G_CALLBACK (addons_deactivate_cb), app); +} + +MidoriExtension* +extension_init (void) +{ + MidoriExtension* extension = g_object_new (MIDORI_TYPE_EXTENSION, + "name", _("User addons"), + "description", _("Support for userscripts and userstyles"), + "version", "0.1", + "authors", "Arno Renevier ", + NULL); + g_signal_connect (extension, "activate", + G_CALLBACK (addons_activate_cb), NULL); + + return extension; +} diff --git a/midori/main.c b/midori/main.c index 2d43bd40..628665c3 100644 --- a/midori/main.c +++ b/midori/main.c @@ -15,7 +15,6 @@ #endif #include "midori.h" -#include "midori-addons.h" #include "midori-array.h" #include "midori-bookmarks.h" #include "midori-console.h" @@ -661,11 +660,6 @@ midori_app_add_browser_cb (MidoriApp* app, 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)); - /* Extensions */ addon = g_object_new (MIDORI_TYPE_EXTENSIONS, NULL); gtk_widget_show (addon); diff --git a/panels/midori-addons.c b/panels/midori-addons.c deleted file mode 100644 index 88061950..00000000 --- a/panels/midori-addons.c +++ /dev/null @@ -1,1079 +0,0 @@ -/* - Copyright (C) 2008 Christian Dywan - Copyright (C) 2008 Arnaud Renevier - - 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-stock.h" - -#include "sokoke.h" - -#include -#include -#include -#include -#include -#include - -#if HAVE_UNISTD_H - #include -#endif - -struct _MidoriAddons -{ - GtkVBox parent_instance; - - GtkWidget* web_widget; - GtkWidget* toolbar; - GtkWidget* treeview; - - GSList* elements; -}; - -struct _MidoriAddonsClass -{ - GtkVBoxClass parent_class; -}; - -struct AddonElement -{ - gchar *fullpath; - gchar *name; - gchar *description; - gboolean enabled; - gboolean broken; - - GSList* includes; - GSList* excludes; -}; - -static void -midori_addons_viewable_iface_init (MidoriViewableIface* iface); - -G_DEFINE_TYPE_WITH_CODE (MidoriAddons, midori_addons, GTK_TYPE_VBOX, - G_IMPLEMENT_INTERFACE (MIDORI_TYPE_VIEWABLE, - midori_addons_viewable_iface_init)); - -enum -{ - PROP_0, - - PROP_KIND, - PROP_WEB_WIDGET -}; - -static void -midori_addons_finalize (GObject* object); - -static void -midori_addons_set_property (GObject* object, - guint prop_id, - const GValue* value, - GParamSpec* pspec); - -static void -midori_addons_get_property (GObject* object, - guint prop_id, - GValue* value, - GParamSpec* pspec); - -GType -midori_addon_kind_get_type (void) -{ - static GType type = 0; - if (!type) - { - static const GEnumValue values[] = { - { MIDORI_ADDON_NONE, "MIDORI_ADDON_NONE", N_("None") }, - { MIDORI_ADDON_USER_SCRIPTS, "MIDORI_USER_SCRIPTS", N_("Userscripts") }, - { MIDORI_ADDON_USER_STYLES, "MIDORI_USER_STYLES", N_("Userstyles") }, - { 0, NULL, NULL } - }; - type = g_enum_register_static ("MidoriAddonKind", values); - } - return type; -} - -static void -midori_addons_class_init (MidoriAddonsClass* class) -{ - GObjectClass* gobject_class; - GParamFlags flags; - - gobject_class = G_OBJECT_CLASS (class); - gobject_class->finalize = midori_addons_finalize; - gobject_class->set_property = midori_addons_set_property; - gobject_class->get_property = midori_addons_get_property; - - flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT; - - g_object_class_install_property (gobject_class, - PROP_KIND, - g_param_spec_enum ( - "kind", - "Kind", - "The kind of addons", - MIDORI_TYPE_ADDON_KIND, - MIDORI_ADDON_NONE, - flags)); - - g_object_class_install_property (gobject_class, - PROP_WEB_WIDGET, - g_param_spec_object ( - "web-widget", - "Web Widget", - "The assigned web widget", - GTK_TYPE_WIDGET, - G_PARAM_READWRITE)); -} - -static const gchar* -midori_addons_get_label (MidoriViewable* viewable) -{ - return _("Userscripts"); -} - -static const gchar* -midori_addons_get_stock_id (MidoriViewable* viewable) -{ - return STOCK_SCRIPTS; -} - -static void -midori_addons_viewable_iface_init (MidoriViewableIface* iface) -{ - iface->get_stock_id = midori_addons_get_stock_id; - iface->get_label = midori_addons_get_label; - iface->get_toolbar = midori_addons_get_toolbar; -} - -static void -midori_addons_set_property (GObject* object, - guint prop_id, - const GValue* value, - GParamSpec* pspec) -{ - MidoriAddons* addons = MIDORI_ADDONS (object); - - switch (prop_id) - { - case PROP_KIND: - /* Ignored */ - break; - case PROP_WEB_WIDGET: - katze_object_assign (addons->web_widget, g_value_dup_object (value)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -midori_addons_get_property (GObject* object, - guint prop_id, - GValue* value, - GParamSpec* pspec) -{ - MidoriAddons* addons = MIDORI_ADDONS (object); - - switch (prop_id) - { - case PROP_KIND: - g_value_set_enum (value, MIDORI_ADDON_USER_SCRIPTS); - break; - case PROP_WEB_WIDGET: - g_value_set_object (value, addons->web_widget); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static GSList* -_addons_get_directories (MidoriAddons* addons) -{ - const gchar* folders[] = { "scripts", "styles" }; - GSList *directories; - guint i; - const char* const* datadirs; - gchar* path; - - directories = NULL; - - for (i = 0; i < G_N_ELEMENTS (folders); i++) - { - path = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), - PACKAGE_NAME, folders[i], NULL); - if (g_access (path, X_OK) == 0) - directories = g_slist_prepend (directories, path); - else - g_free (path); - - datadirs = g_get_system_data_dirs (); - while (*datadirs) - { - path = g_build_path (G_DIR_SEPARATOR_S, *datadirs, - PACKAGE_NAME, folders[i], NULL); - if (g_access (path, X_OK) == 0) - directories = g_slist_prepend (directories, path); - else - g_free (path); - datadirs++; - } - } - - return directories; -} - -static GSList* -_addons_get_files (MidoriAddons* addons) -{ - GSList* files; - GDir* addon_dir; - GSList* list; - GSList* directories; - const gchar* filename; - gchar* dirname; - gchar* fullname; - - files = NULL; - - directories = _addons_get_directories (addons); - list = directories; - while (directories) - { - dirname = directories->data; - if ((addon_dir = g_dir_open (dirname, 0, NULL))) - { - while ((filename = g_dir_read_name (addon_dir))) - { - if (g_str_has_suffix (filename, ".js") - || g_str_has_suffix (filename, ".css")) - { - fullname = g_build_filename (dirname, filename, NULL); - files = g_slist_prepend (files, fullname); - } - } - g_dir_close (addon_dir); - } - g_free (dirname); - directories = g_slist_next (directories); - } - g_slist_free (list); - - return files; -} - -static void -midori_addons_directory_monitor_changed (GFileMonitor* monitor, - GFile* child, - GFile* other_file, - GFileMonitorEvent flags, - MidoriAddons* addons) -{ - midori_addons_update_elements (addons); -} - -static void -midori_addons_button_add_clicked_cb (GtkToolItem* toolitem, - MidoriAddons* addons) -{ - gchar* path_scripts, *path_styles; - GtkWidget* dialog; - - path_scripts = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), - PACKAGE_NAME, "scripts", NULL); - path_styles = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (), - PACKAGE_NAME, "styles", NULL); - dialog = gtk_message_dialog_new ( - GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (addons))), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, - _("Copy userscripts to the folder %s and " - "copy userstyles to the folder %s."), path_scripts, path_styles); - g_free (path_scripts); - g_free (path_styles); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -static void -midori_addons_treeview_render_tick_cb (GtkTreeViewColumn* column, - GtkCellRenderer* renderer, - GtkTreeModel* model, - GtkTreeIter* iter, - GtkWidget* treeview) -{ - struct AddonElement *element; - - gtk_tree_model_get (model, iter, 0, &element, -1); - - g_object_set (renderer, - "active", element->enabled, - "sensitive", !element->broken, - NULL); -} - -static void -midori_addons_treeview_render_text_cb (GtkTreeViewColumn* column, - GtkCellRenderer* renderer, - GtkTreeModel* model, - GtkTreeIter* iter, - GtkWidget* treeview) -{ - struct AddonElement *element; - - gtk_tree_model_get (model, iter, 0, &element, -1); - - g_object_set (renderer, "text", element->name, NULL); - if (!element->enabled) - g_object_set (renderer, "sensitive", false, NULL); - else - g_object_set (renderer, "sensitive", true, NULL); -} - -static void -midori_addons_treeview_row_activated_cb (GtkTreeView* treeview, - GtkTreePath* path, - GtkTreeViewColumn* column, - MidoriAddons* addons) -{ - GtkTreeModel* model = gtk_tree_view_get_model (treeview); - GtkTreeIter iter; - if (gtk_tree_model_get_iter (model, &iter, path)) - { - struct AddonElement *element; - - gtk_tree_model_get (model, &iter, 0, &element, -1); - - element->enabled = !element->enabled; - - /* After enabling or disabling an element, the tree view - is not updated automatically; we need to notify tree model - in order to take the modification into account */ - gtk_tree_model_row_changed (model, path, &iter); - } -} - -static void -midori_addons_cell_renderer_toggled_cb (GtkCellRendererToggle* renderer, - const gchar* path, - MidoriAddons* addons) -{ - GtkTreeModel* model; - GtkTreeIter iter; - - model = gtk_tree_view_get_model (GTK_TREE_VIEW (addons->treeview)); - if (gtk_tree_model_get_iter_from_string (model, &iter, path)) - { - struct AddonElement *element; - GtkTreePath* tree_path; - - gtk_tree_model_get (model, &iter, 0, &element, -1); - - element->enabled = !element->enabled; - - /* After enabling or disabling an element, the tree view - is not updated automatically; we need to notify tree model - in order to take the modification into account */ - tree_path = gtk_tree_path_new_from_string (path); - gtk_tree_model_row_changed (model, tree_path, &iter); - gtk_tree_path_free (tree_path); - } -} - -static void -midori_addons_init (MidoriAddons* addons) -{ - GtkTreeViewColumn* column; - GtkCellRenderer* renderer_text; - GtkCellRenderer* renderer_toggle; - - addons->web_widget = NULL; - addons->elements = NULL; - - addons->treeview = gtk_tree_view_new (); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (addons->treeview), FALSE); - column = gtk_tree_view_column_new (); - renderer_toggle = gtk_cell_renderer_toggle_new (); - gtk_tree_view_column_pack_start (column, renderer_toggle, FALSE); - gtk_tree_view_column_set_cell_data_func (column, renderer_toggle, - (GtkTreeCellDataFunc)midori_addons_treeview_render_tick_cb, - addons->treeview, NULL); - g_signal_connect (renderer_toggle, "toggled", - G_CALLBACK (midori_addons_cell_renderer_toggled_cb), addons); - gtk_tree_view_append_column (GTK_TREE_VIEW (addons->treeview), column); - column = gtk_tree_view_column_new (); - renderer_text = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer_text, FALSE); - gtk_tree_view_column_set_cell_data_func (column, renderer_text, - (GtkTreeCellDataFunc)midori_addons_treeview_render_text_cb, - addons->treeview, NULL); - gtk_tree_view_append_column (GTK_TREE_VIEW (addons->treeview), column); - g_signal_connect (addons->treeview, "row-activated", - G_CALLBACK (midori_addons_treeview_row_activated_cb), - addons); - gtk_widget_show (addons->treeview); - gtk_box_pack_start (GTK_BOX (addons), addons->treeview, TRUE, TRUE, 0); -} - -static void -midori_addons_finalize (GObject* object) -{ - MidoriAddons* addons = MIDORI_ADDONS (object); - - katze_object_assign (addons->web_widget, NULL); - - g_slist_free (addons->elements); -} - -static gboolean -js_metadata_from_file (const gchar* filename, - GSList** includes, - GSList** excludes, - gchar** name, - gchar** description) -{ - GIOChannel* channel; - gboolean found_meta; - gchar* line; - gchar* rest_of_line; - - if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) - return FALSE; - - channel = g_io_channel_new_file (filename, "r", 0); - if (!channel) - return FALSE; - - found_meta = FALSE; - - while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL) - == G_IO_STATUS_NORMAL) - { - if (g_str_has_prefix (line, "// ==UserScript==")) - found_meta = TRUE; - else if (found_meta) - { - if (g_str_has_prefix (line, "// ==/UserScript==")) - found_meta = FALSE; - else if (g_str_has_prefix (line, "// @require ") || - g_str_has_prefix (line, "// @resource ")) - { - /* We don't support these, so abort here */ - g_free (line); - g_io_channel_shutdown (channel, false, 0); - g_slist_free (*includes); - g_slist_free (*excludes); - *includes = NULL; - *excludes = NULL; - return FALSE; - } - else if (includes && g_str_has_prefix (line, "// @include ")) - { - rest_of_line = g_strdup (line + strlen ("// @include ")); - rest_of_line = g_strstrip (rest_of_line); - *includes = g_slist_prepend (*includes, rest_of_line); - } - else if (excludes && g_str_has_prefix (line, "// @exclude ")) - { - rest_of_line = g_strdup (line + strlen ("// @exclude ")); - rest_of_line = g_strstrip (rest_of_line); - *excludes = g_slist_prepend (*excludes, rest_of_line); - } - else if (name && g_str_has_prefix (line, "// @name ")) - { - rest_of_line = g_strdup (line + strlen ("// @name ")); - rest_of_line = g_strstrip (rest_of_line); - *name = rest_of_line; - } - else if (description && g_str_has_prefix (line, "// @description ")) - { - rest_of_line = g_strdup (line + strlen ("// @description ")); - rest_of_line = g_strstrip (rest_of_line); - *description = rest_of_line; - } - } - g_free (line); - } - g_io_channel_shutdown (channel, false, 0); - g_io_channel_unref (channel); - - return TRUE; -} - -static gboolean -css_metadata_from_file (const gchar* filename, - GSList** includes, - GSList** excludes) -{ - GIOChannel* channel; - gchar* line; - gchar* rest_of_line; - - if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)) - return FALSE; - - channel = g_io_channel_new_file (filename, "r", 0); - if (!channel) - return FALSE; - - while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL) - == G_IO_STATUS_NORMAL) - { - if (g_str_has_prefix (line, "@namespace")) - ; /* FIXME: Check "http://www.w3.org/1999/xhtml", skip otherwise */ - else if (g_str_has_prefix (line, "@-moz-document")) - { /* FIXME: We merely look for includes. We should honor blocks. */ - if (includes) - { - gchar** parts; - guint i; - - rest_of_line = g_strdup (line + strlen ("@-moz-document")); - rest_of_line = g_strstrip (rest_of_line); - parts = g_strsplit (rest_of_line, " ", 0); - i = 0; - while (parts[i]) - { - if (g_str_has_prefix (parts[i], "url-prefix(")) - { - gchar* value = g_strdup (parts[i] + strlen ("url-prefix(")); - guint j; - - if (value[0] != '\'' && value[0] != '"') - { - /* Wrong syntax, abort */ - g_free (value); - g_strfreev (parts); - g_free (line); - g_io_channel_shutdown (channel, false, 0); - g_slist_free (*includes); - g_slist_free (*excludes); - *includes = NULL; - *excludes = NULL; - return FALSE; - } - j = 1; - while (value[j] != '\0') - { - if (value[j] == value[0]) - break; - j++; - } - *includes = g_slist_prepend (*includes, g_strndup (value + 1, j - 1)); - g_free (value); - } - /* FIXME: Recognize "domain" */ - i++; - } - g_strfreev (parts); - } - } - g_free (line); - } - g_io_channel_shutdown (channel, false, 0); - g_io_channel_unref (channel); - - return TRUE; -} - -static gchar* -_convert_to_simple_regexp (const gchar* pattern) -{ - guint len; - gchar* dest; - guint pos; - guint i; - gchar c; - - len = strlen (pattern); - dest = g_malloc0 (len * 2 + 1); - dest[0] = '^'; - pos = 1; - - for (i = 0; i < len; i++) - { - c = pattern[i]; - switch (c) - { - case '*': - dest[pos] = '.'; - dest[pos + 1] = c; - pos++; - pos++; - break; - case '.' : - case '?' : - case '^' : - case '$' : - case '+' : - case '{' : - case '[' : - case '|' : - case '(' : - case ')' : - case ']' : - case '\\' : - dest[pos] = '\\'; - dest[pos + 1] = c; - pos++; - pos++; - break; - case ' ' : - break; - default: - dest[pos] = pattern[i]; - pos ++; - } - } - return dest; -} - -static gboolean -_may_load_script (const gchar* uri, - GSList** includes, - GSList** excludes) -{ - gboolean match; - GSList* list; - gchar* re; - - if (*includes) - match = FALSE; - else - match = TRUE; - - list = *includes; - while (list) - { - re = _convert_to_simple_regexp (list->data); - if (g_regex_match_simple (re, uri, 0, 0)) - { - match = TRUE; - break; - } - g_free (re); - list = g_slist_next (list); - } - if (!match) - { - return FALSE; - } - list = *excludes; - while (list) - { - re = _convert_to_simple_regexp (list->data); - if (g_regex_match_simple (re, uri, 0, 0)) - { - match = FALSE; - break; - } - g_free (re); - list = g_slist_next (list); - } - return match; -} - -static gboolean -_js_script_from_file (JSContextRef js_context, - const gchar* filename, - gchar** exception) -{ - gboolean result = FALSE; - gchar* script; - GError* error = NULL; - gchar* wrapped_script; - - if (g_file_get_contents (filename, &script, NULL, &error)) - { - /* Wrap the script to prevent global variables */ - wrapped_script = g_strdup_printf ( - "window.addEventListener ('DOMContentLoaded'," - "function () { %s }, true);", script); - if (sokoke_js_script_eval (js_context, wrapped_script, exception)) - result = TRUE; - g_free (wrapped_script); - g_free (script); - } - else - { - *exception = g_strdup (error->message); - g_error_free (error); - } - return result; -} - -static gboolean -_js_style_from_file (JSContextRef js_context, - const gchar* filename, - gchar** exception) -{ - gboolean result; - gchar* style; - GError* error; - guint i, n; - gchar* style_script; - - result = FALSE; - error = NULL; - if (g_file_get_contents (filename, &style, NULL, &error)) - { - guint meta = 0; - n = strlen (style); - for (i = 0; i < n; i++) - { - /* Replace line breaks with spaces */ - if (style[i] == '\n' || style[i] == '\r') - style[i] = ' '; - /* Change all single quotes to double quotes */ - if (style[i] == '\'') - style[i] = '\"'; - /* Turn metadata we inspected earlier into comments */ - if (!meta && style[i] == '@') - { - style[i] = '/'; - meta++; - } - else if (meta == 1 && (style[i] == '-' || style[i] == 'n')) - { - style[i] = '*'; - meta++; - } - else if (meta == 2 && style[i] == '{') - { - style[i - 1] = '*'; - style[i] = '/'; - meta++; - } - else if (meta == 3 && style[i] == '{') - meta++; - else if (meta == 4 && style[i] == '}') - meta--; - else if (meta == 3 && style[i] == '}') - { - style[i] = ' '; - meta = 0; - } - } - - style_script = g_strdup_printf ( - "window.addEventListener ('DOMContentLoaded'," - "function () {" - "var mystyle = document.createElement(\"style\");" - "mystyle.setAttribute(\"type\", \"text/css\");" - "mystyle.appendChild(document.createTextNode('%s'));" - "var head = document.getElementsByTagName(\"head\")[0];" - "if (head) head.appendChild(mystyle);" - "else document.documentElement.insertBefore(mystyle, document.documentElement.firstChild);" - "}, true);", - style); - if (sokoke_js_script_eval (js_context, style_script, exception)) - result = TRUE; - g_free (style_script); - g_free (style); - } - else - { - *exception = g_strdup (error->message); - g_error_free (error); - } - return result; -} - -static void -midori_web_widget_context_ready_cb (GtkWidget* web_widget, - JSGlobalContextRef js_context, - MidoriAddons* addons) -{ - gchar* uri; - GSList* elements; - struct AddonElement* element; - gchar* fullname; - gchar* exception; - gchar* message; - - uri = katze_object_get_string (web_widget, "uri"); - /* Don't run scripts or styles on blank or special pages */ - if (!(uri && *uri && strncmp (uri, "about:", 6))) - { - g_free (uri); - return; - } - - elements = addons->elements; - while (elements) - { - element = elements->data; - if (!element->enabled || element->broken) - { - elements = g_slist_next (elements); - continue; - } - - fullname = element->fullpath; - - if (element->includes || element->excludes) - if (!_may_load_script (uri, &element->includes, &element->excludes)) - { - elements = g_slist_next (elements); - continue; - } - - exception = NULL; - if (g_str_has_suffix (fullname, ".js") && - !_js_script_from_file (js_context, fullname, &exception)) - { - message = g_strdup_printf ("console.error ('%s');", exception); - sokoke_js_script_eval (js_context, message, NULL); - g_free (message); - g_free (exception); - } - else if (g_str_has_suffix (fullname, ".css") && - !_js_style_from_file (js_context, fullname, &exception)) - { - message = g_strdup_printf ("console.error ('%s');", exception); - sokoke_js_script_eval (js_context, message, NULL); - g_free (message); - g_free (exception); - } - - elements = g_slist_next (elements); - } - - g_free (uri); -} - -/** - * midori_addons_new: - * @kind: the kind of addon - * @web_widget: a web widget - * - * Creates a new addons widget. - * - * @web_widget can be one of the following: - * %MidoriBrowser, %MidoriWebView, %WebKitWebView - * - * Return value: a new #MidoriAddons - **/ -GtkWidget* -midori_addons_new (MidoriAddonKind kind, - GtkWidget* web_widget) -{ - MidoriAddons* addons; - GSList* directories; - GSList* list; - GFile* directory; - GError* error; - GFileMonitor* monitor; - - g_return_val_if_fail (GTK_IS_WIDGET (web_widget), NULL); - - addons = g_object_new (MIDORI_TYPE_ADDONS, - "kind", kind, - "web-widget", web_widget, - NULL); - - if (kind == MIDORI_ADDON_USER_SCRIPTS || kind == MIDORI_ADDON_USER_STYLES) - g_signal_connect (addons->web_widget, "context-ready", - G_CALLBACK (midori_web_widget_context_ready_cb), addons); - - midori_addons_update_elements (addons); - - directories = _addons_get_directories (addons); - list = directories; - while (directories) - { - directory = g_file_new_for_path (directories->data); - directories = g_slist_next (directories); - error = NULL; - monitor = g_file_monitor_directory (directory, - G_FILE_MONITOR_NONE, - NULL, &error); - if (monitor) - g_signal_connect (monitor, "changed", - G_CALLBACK (midori_addons_directory_monitor_changed), addons); - else - { - g_warning (_("Can't monitor folder '%s': %s"), - g_file_get_parse_name (directory), error->message); - g_error_free (error); - } - g_object_unref (directory); - } - g_slist_free (list); - - return GTK_WIDGET (addons); -} - -/** - * midori_addons_get_toolbar: - * @addons: a #MidoriAddons - * - * Retrieves the toolbar of the addons. A new widget - * is created on the first call of this function. - * - * Return value: a toolbar widget - * - * Deprecated: 0.1.2: Use midori_viewable_get_toolbar() instead. - **/ -GtkWidget* -midori_addons_get_toolbar (MidoriViewable* addons) -{ - GtkWidget* toolbar; - GtkToolItem* toolitem; - - g_return_val_if_fail (MIDORI_IS_ADDONS (addons), NULL); - - if (!MIDORI_ADDONS (addons)->toolbar) - { - toolbar = gtk_toolbar_new (); - gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ); - gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON); - toolitem = gtk_tool_item_new (); - gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); - gtk_widget_show (GTK_WIDGET (toolitem)); - - /* separator */ - toolitem = gtk_separator_tool_item_new (); - gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (toolitem), - FALSE); - gtk_tool_item_set_expand (toolitem, TRUE); - gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); - gtk_widget_show (GTK_WIDGET (toolitem)); - - /* add button */ - toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_ADD); - gtk_tool_item_set_is_important (toolitem, TRUE); - g_signal_connect (toolitem, "clicked", - G_CALLBACK (midori_addons_button_add_clicked_cb), addons); - gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); - gtk_widget_show (GTK_WIDGET (toolitem)); - MIDORI_ADDONS (addons)->toolbar = toolbar; - - g_signal_connect (toolbar, "destroy", - G_CALLBACK (gtk_widget_destroyed), - &MIDORI_ADDONS (addons)->toolbar); - } - - return MIDORI_ADDONS (addons)->toolbar; -} - -/** - * midori_addons_update_elements: - * @addons: a #MidoriAddons - * - * Updates all addons elements (file paths and metadata). - * - **/ -void -midori_addons_update_elements (MidoriAddons* addons) -{ - GTree* disabled; - GSList* elements; - gboolean broken; - gchar* fullname; - gchar* displayname; - gchar* name; - gchar* description; - GSList* includes; - GSList* excludes; - GtkListStore* liststore; - GtkTreeIter iter; - GSList* addon_files; - GSList* list; - struct AddonElement* element; - - g_return_if_fail (MIDORI_IS_ADDONS (addons)); - - /* FIXME: would GHashTable be better? */ - disabled = g_tree_new ((GCompareFunc)strcmp); - elements = addons->elements; - while (elements) - { - element = elements->data; - if (!element->enabled) - g_tree_insert (disabled, element->fullpath, NULL); - elements = g_slist_next (elements); - } - - g_slist_free (addons->elements); - addons->elements = NULL; - - liststore = gtk_list_store_new (3, G_TYPE_POINTER, - G_TYPE_INT, - G_TYPE_STRING); - - addon_files = _addons_get_files (addons); - list = addon_files; - while (addon_files) - { - fullname = addon_files->data; - displayname = g_filename_display_basename (fullname); - description = NULL; - includes = NULL; - excludes = NULL; - broken = FALSE; - - if (g_str_has_suffix (fullname, ".js")) - { - name = NULL; - if (!js_metadata_from_file (fullname, &includes, &excludes, - &name, &description)) - broken = TRUE; - - if (name) - { - g_free (displayname); - displayname = name; - } - } - else if (g_str_has_suffix (fullname, ".css")) - { - if (!css_metadata_from_file (fullname, &includes, &excludes)) - broken = TRUE; - } - - element = g_new (struct AddonElement, 1); - element->name = displayname; - element->description = description; - element->fullpath = fullname; - - if (g_tree_lookup_extended (disabled, fullname, NULL, NULL)) - element->enabled = FALSE; - else - element->enabled = TRUE; - element->broken = broken; - element->includes = includes; - element->excludes = excludes; - addons->elements = g_slist_prepend (addons->elements, element); - - gtk_list_store_append (liststore, &iter); - gtk_list_store_set (liststore, &iter, - 0, element, 1, 0, 2, "", -1); - - addon_files = g_slist_next (addon_files); - } - addons->elements = g_slist_reverse (addons->elements); - - g_tree_destroy (disabled); - g_slist_free (list); - - gtk_tree_view_set_model (GTK_TREE_VIEW (addons->treeview), - GTK_TREE_MODEL (liststore)); - - gtk_widget_queue_draw (GTK_WIDGET (addons->treeview)); -} diff --git a/panels/midori-addons.h b/panels/midori-addons.h deleted file mode 100644 index 662745ab..00000000 --- a/panels/midori-addons.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - Copyright (C) 2008 Christian Dywan - - 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. -*/ - -#ifndef __MIDORI_ADDONS_H__ -#define __MIDORI_ADDONS_H__ - -#include - -#include - -#include "midori-viewable.h" - -G_BEGIN_DECLS - -#define MIDORI_TYPE_ADDONS \ - (midori_addons_get_type ()) -#define MIDORI_ADDONS(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), MIDORI_TYPE_ADDONS, MidoriAddons)) -#define MIDORI_ADDONS_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), MIDORI_TYPE_ADDONS, MidoriAddonsClass)) -#define MIDORI_IS_ADDONS(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MIDORI_TYPE_ADDONS)) -#define MIDORI_IS_ADDONS_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), MIDORI_TYPE_ADDONS)) -#define MIDORI_ADDONS_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), MIDORI_TYPE_ADDONS, MidoriAddonsClass)) - -typedef struct _MidoriAddons MidoriAddons; -typedef struct _MidoriAddonsClass MidoriAddonsClass; - -typedef enum -{ - MIDORI_ADDON_NONE, - MIDORI_ADDON_USER_SCRIPTS, - MIDORI_ADDON_USER_STYLES -} MidoriAddonKind; - -GType -midori_addon_kind_get_type (void) G_GNUC_CONST; - -#define MIDORI_TYPE_ADDON_KIND \ - (midori_addon_kind_get_type ()) - -GType -midori_addons_get_type (void); - -GtkWidget* -midori_addons_new (MidoriAddonKind kind, - GtkWidget* web_widget); - -GtkWidget* -midori_addons_get_toolbar (MidoriViewable* addons); - -void -midori_addons_update_elements (MidoriAddons* addons); - -G_END_DECLS - -#endif /* __MIDORI_ADDONS_H__ */