From 787d591681df40c8b10c450273d1c827b6eb818b Mon Sep 17 00:00:00 2001 From: William Jon McCann Date: Mon, 10 Dec 2012 12:31:12 +0100 Subject: Use better icons for webapps https://bugzilla.gnome.org/show_bug.cgi?id=657755 --- lib/ephy-web-app-utils.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++ lib/ephy-web-app-utils.h | 10 +++ src/window-commands.c | 196 +++++++++++++++++++++++++++++++++------- 3 files changed, 402 insertions(+), 32 deletions(-) diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c index 1bd1a2f8d..203a771f8 100644 --- a/lib/ephy-web-app-utils.c +++ b/lib/ephy-web-app-utils.c @@ -35,6 +35,234 @@ #define EPHY_WEB_APP_DESKTOP_FILE_PREFIX "epiphany-" +static char * +resolve_uri (WebKitWebView *view, + const char *uri) +{ + SoupURI *base; + SoupURI *new; + const char *base_uri; + char *ret; + + if (uri == NULL) + return NULL; + + base_uri = webkit_web_view_get_uri (view); + if (base_uri == NULL) + return NULL; + + base = soup_uri_new (base_uri); + new = soup_uri_new_with_base (base, uri); + soup_uri_free (base); + ret = soup_uri_to_string (new, FALSE); + soup_uri_free (new); + + return ret; +} + +#ifdef HAVE_WEBKIT2 +/* TODO: DOM Bindindgs */ +#else +static gboolean +get_icon_from_mstile (WebKitWebView *view, + char **uri_out, + char **color_out) +{ + gboolean ret; + WebKitDOMDocument *document; + WebKitDOMNodeList *metas; + gulong length, i; + char *image = NULL; + char *color = NULL; + + document = webkit_web_view_get_dom_document (view); + metas = webkit_dom_document_get_elements_by_tag_name (document, "meta"); + length = webkit_dom_node_list_get_length (metas); + + for (i = 0; i < length; i++) { + WebKitDOMNode *node = webkit_dom_node_list_item (metas, i); + char *name; + + name = webkit_dom_html_meta_element_get_name (WEBKIT_DOM_HTML_META_ELEMENT (node)); + if (g_strcmp0 (name, "msapplication-TileImage") == 0) { + if (image == NULL) + image = webkit_dom_html_meta_element_get_content (WEBKIT_DOM_HTML_META_ELEMENT (node)); + } else if (g_strcmp0 (name, "msapplication-TileColor") == 0) { + if (color == NULL) + color = webkit_dom_html_meta_element_get_content (WEBKIT_DOM_HTML_META_ELEMENT (node)); + } + } + + ret = (image != NULL && *image != '\0'); + + if (uri_out != NULL) + *uri_out = g_strdup (image); + if (color_out != NULL) + *color_out = g_strdup (color); + + g_free (image); + g_free (color); + + return ret; +} + +static gboolean +get_icon_from_ogp (WebKitWebView *view, + char **uri_out, + char **color_out) +{ + gboolean ret; + WebKitDOMDocument *document; + WebKitDOMNodeList *metas; + gulong length, i; + char *image = NULL; + char *color = NULL; + + document = webkit_web_view_get_dom_document (view); + metas = webkit_dom_document_get_elements_by_tag_name (document, "meta"); + length = webkit_dom_node_list_get_length (metas); + + for (i = 0; i < length && image == NULL; i++) { + WebKitDOMNode *node = webkit_dom_node_list_item (metas, i); + char *property; + char *itemprop; + + property = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "property"); + itemprop = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "itemprop"); + if (g_strcmp0 (property, "og:image") == 0 || + g_strcmp0 (itemprop, "image") == 0) { + image = webkit_dom_html_meta_element_get_content (WEBKIT_DOM_HTML_META_ELEMENT (node)); + } + g_free (property); + g_free (itemprop); + } + + ret = (image != NULL && *image != '\0'); + + if (uri_out != NULL) + *uri_out = g_strdup (image); + if (color_out != NULL) + *color_out = g_strdup (color); + + return ret; +} + +static gboolean +get_icon_from_touch_icon (WebKitWebView *view, + char **uri_out, + char **color_out) +{ + gboolean ret; + WebKitDOMDocument *document; + WebKitDOMNodeList *links; + gulong length, i; + char *image = NULL; + char *color = NULL; + + document = webkit_web_view_get_dom_document (view); + links = webkit_dom_document_get_elements_by_tag_name (document, "link"); + length = webkit_dom_node_list_get_length (links); + + for (i = 0; i < length && image == NULL; i++) { + char *rel; + WebKitDOMNode *node = webkit_dom_node_list_item (links, i); + + rel = webkit_dom_html_link_element_get_rel (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); + /* TODO: support more than one possible icon. */ + if (g_strcmp0 (rel, "apple-touch-icon") == 0 || + g_strcmp0 (rel, "apple-touch-icon-precomposed") == 0) { + image = webkit_dom_html_link_element_get_href (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); + } + g_free (rel); + } + + ret = (image != NULL && *image != '\0'); + + if (uri_out != NULL) + *uri_out = g_strdup (image); + if (color_out != NULL) + *color_out = g_strdup (color); + + return ret; +} + +static gboolean +get_icon_from_favicon (WebKitWebView *view, + char **uri_out, + char **color_out) +{ + gboolean ret; + WebKitDOMDocument *document; + WebKitDOMNodeList *links; + gulong length, i; + char *image = NULL; + char *color = NULL; + + document = webkit_web_view_get_dom_document (view); + links = webkit_dom_document_get_elements_by_tag_name (document, "link"); + length = webkit_dom_node_list_get_length (links); + + for (i = 0; i < length; i++) { + char *rel; + WebKitDOMNode *node = webkit_dom_node_list_item (links, i); + + rel = webkit_dom_html_link_element_get_rel (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); + if (g_strcmp0 (rel, "shortcut-icon") == 0 || + g_strcmp0 (rel, "shortcut icon") == 0 || + g_strcmp0 (rel, "icon shortcut") == 0 || + g_strcmp0 (rel, "icon") == 0) { + image = webkit_dom_html_link_element_get_href (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); + } + g_free (rel); + } + + ret = (image != NULL && *image != '\0'); + + if (uri_out != NULL) + *uri_out = g_strdup (image); + if (color_out != NULL) + *color_out = g_strdup (color); + + return ret; +} +#endif /* HAVE_WEBKIT2 */ + +gboolean +ephy_web_view_get_best_icon (WebKitWebView *view, + char **uri, + GdkRGBA *rgba) +{ + gboolean ret = FALSE; + char *image = NULL; + char *color = NULL; + +#ifdef HAVE_WEBKIT2 + /* TODO: DOM Bindindgs */ +#else + + /* First try to get a mstile, then try OGP, then favicon */ + + ret = get_icon_from_mstile (view, &image, &color); + if (! ret) + ret = get_icon_from_ogp (view, &image, &color); + if (! ret) + ret = get_icon_from_touch_icon (view, &image, &color); + if (! ret) + ret = get_icon_from_favicon (view, &image, &color); + +#endif + + if (uri != NULL) + *uri = resolve_uri (view, image); + if (rgba != NULL && color != NULL) + gdk_rgba_parse (rgba, color); + + g_free (image); + g_free (color); + + return ret; +} + /* This is necessary because of gnome-shell's guessing of a .desktop filename from WM_CLASS property. */ static char * diff --git a/lib/ephy-web-app-utils.h b/lib/ephy-web-app-utils.h index 54968df80..adb254e5f 100644 --- a/lib/ephy-web-app-utils.h +++ b/lib/ephy-web-app-utils.h @@ -27,6 +27,12 @@ #include #include +#ifdef HAVE_WEBKIT2 +#include +#else +#include +#endif + G_BEGIN_DECLS typedef struct { @@ -52,6 +58,10 @@ void ephy_web_application_free_application_list (GList *list); gboolean ephy_web_application_exists (const char *name); +gboolean ephy_web_view_get_best_icon (WebKitWebView *view, + char **uri, + GdkRGBA *rgba); + G_END_DECLS #endif diff --git a/src/window-commands.c b/src/window-commands.c index bd6902f85..da9393476 100644 --- a/src/window-commands.c +++ b/src/window-commands.c @@ -65,6 +65,8 @@ #include #endif +#define DEFAULT_ICON_SIZE 144 + void window_cmd_file_print (GtkAction *action, EphyWindow *window) @@ -370,6 +372,7 @@ typedef struct { GtkWidget *spinner; GtkWidget *box; char *icon_href; + GdkRGBA icon_rgba; } EphyApplicationDialogData; static void @@ -379,19 +382,155 @@ ephy_application_dialog_data_free (EphyApplicationDialogData *data) g_slice_free (EphyApplicationDialogData, data); } +static void +rounded_rectangle (cairo_t *cr, + gdouble aspect, + gdouble x, + gdouble y, + gdouble corner_radius, + gdouble width, + gdouble height) +{ + gdouble radius; + gdouble degrees; + + radius = corner_radius / aspect; + degrees = G_PI / 180.0; + + cairo_new_sub_path (cr); + cairo_arc (cr, + x + width - radius, + y + radius, + radius, + -90 * degrees, + 0 * degrees); + cairo_arc (cr, + x + width - radius, + y + height - radius, + radius, + 0 * degrees, + 90 * degrees); + cairo_arc (cr, + x + radius, + y + height - radius, + radius, + 90 * degrees, + 180 * degrees); + cairo_arc (cr, + x + radius, + y + radius, + radius, + 180 * degrees, + 270 * degrees); + cairo_close_path (cr); +} + +static GdkPixbuf * +frame_pixbuf (GdkPixbuf *pixbuf, + GdkRGBA *rgba, + int width, + int height) +{ + GdkPixbuf *framed; + cairo_surface_t *surface; + cairo_t *cr; + int frame_width; + int radius; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + cr = cairo_create (surface); + + frame_width = 0; + radius = 20; + + rounded_rectangle (cr, + 1.0, + frame_width + 0.5, + frame_width + 0.5, + radius, + width - frame_width * 2 - 1, + height - frame_width * 2 - 1); + if (rgba != NULL) + cairo_set_source_rgba (cr, + rgba->red, + rgba->green, + rgba->blue, + rgba->alpha); + else + cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); + cairo_fill_preserve (cr); + + if (pixbuf != NULL) { + GdkPixbuf *scaled; + int w; + int h; + + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + if (w < 48 || h < 48) { + scaled = gdk_pixbuf_scale_simple (pixbuf, w * 3, h * 3, GDK_INTERP_NEAREST); + } else if (w > width || h > height) { + double ws, hs, s; + + ws = (double) width / w; + hs = (double) height / h; + s = MIN (ws, hs); + scaled = gdk_pixbuf_scale_simple (pixbuf, w * s, h * s, GDK_INTERP_BILINEAR); + } else { + scaled = g_object_ref (pixbuf); + } + + w = gdk_pixbuf_get_width (scaled); + h = gdk_pixbuf_get_height (scaled); + + gdk_cairo_set_source_pixbuf (cr, scaled, + (width - w) / 2, + (height - h) / 2); + g_object_unref (scaled); + cairo_fill (cr); + } + + framed = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height); + cairo_destroy (cr); + cairo_surface_destroy (surface); + + return framed; +} + static void take_page_snapshot_and_set_image (EphyApplicationDialogData *data) { GdkPixbuf *snapshot; + GdkPixbuf *framed; int x, y, w, h; x = y = 0; - w = h = 128; /* GNOME hi-res icon size. */ + w = h = DEFAULT_ICON_SIZE; snapshot = ephy_web_view_get_snapshot (data->view, x, y, w, h); - - gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), snapshot); + framed = frame_pixbuf (snapshot, NULL, w, h); g_object_unref (snapshot); + gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), framed); + g_object_unref (framed); +} + +static void +set_app_icon_from_filename (EphyApplicationDialogData *data, + const char *filename) +{ + GdkPixbuf *pixbuf; + GdkPixbuf *framed; + + pixbuf = gdk_pixbuf_new_from_file (filename, NULL); + if (pixbuf == NULL) + return; + + framed = frame_pixbuf (pixbuf, &data->icon_rgba, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE); + g_object_unref (pixbuf); + gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), framed); + g_object_unref (framed); } #ifdef HAVE_WEBKIT2 @@ -402,7 +541,7 @@ download_finished_cb (WebKitDownload *download, char *filename; filename = g_filename_from_uri (webkit_download_get_destination (download), NULL, NULL); - gtk_image_set_from_file (GTK_IMAGE (data->image), filename); + set_app_icon_from_filename (data, filename); g_free (filename); } @@ -429,7 +568,7 @@ download_status_changed_cb (WebKitDownload *download, case WEBKIT_DOWNLOAD_STATUS_FINISHED: filename = g_filename_from_uri (webkit_download_get_destination_uri (download), NULL, NULL); - gtk_image_set_from_file (GTK_IMAGE (data->image), filename); + set_app_icon_from_filename (data, filename); g_free (filename); break; case WEBKIT_DOWNLOAD_STATUS_ERROR: @@ -486,39 +625,28 @@ download_icon_and_set_image (EphyApplicationDialogData *data) #endif } + static void fill_default_application_image (EphyApplicationDialogData *data) { -#ifdef HAVE_WEBKIT2 - /* TODO: DOM Bindindgs */ -#else - WebKitDOMDocument *document; - WebKitDOMNodeList *links; - gulong length, i; + gboolean res; - document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (data->view)); - links = webkit_dom_document_get_elements_by_tag_name (document, "link"); - length = webkit_dom_node_list_get_length (links); + data->icon_rgba.red = 0.5; + data->icon_rgba.green = 0.5; + data->icon_rgba.blue = 0.5; + data->icon_rgba.alpha = 0.3; - for (i = 0; i < length; i++) + res = ephy_web_view_get_best_icon (WEBKIT_WEB_VIEW (data->view), + &data->icon_href, + &data->icon_rgba); + if (res) { - char *rel; - WebKitDOMNode *node = webkit_dom_node_list_item (links, i); - rel = webkit_dom_html_link_element_get_rel (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); - /* TODO: support more than one possible icon. */ - if (g_strcmp0 (rel, "apple-touch-icon") == 0 || - g_strcmp0 (rel, "apple-touch-icon-precomposed") == 0) - { - data->icon_href = webkit_dom_html_link_element_get_href (WEBKIT_DOM_HTML_LINK_ELEMENT (node)); - download_icon_and_set_image (data); - g_free (rel); - return; - } + download_icon_and_set_image (data); + } + else + { + take_page_snapshot_and_set_image (data); } -#endif - /* If we make it here, no "apple-touch-icon" link was - * found. Take a snapshot of the page. */ - take_page_snapshot_and_set_image (data); } typedef struct { @@ -718,6 +846,7 @@ window_cmd_file_save_as_application (GtkAction *action, GtkWidget *dialog, *box, *image, *entry, *content_area; EphyWebView *view; EphyApplicationDialogData *data; + GdkPixbuf *pixbuf; embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window)); g_return_if_fail (embed != NULL); @@ -742,8 +871,11 @@ window_cmd_file_save_as_application (GtkAction *action, gtk_container_add (GTK_CONTAINER (content_area), box); image = gtk_image_new (); - gtk_widget_set_size_request (image, 128, 128); + gtk_widget_set_size_request (image, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE); gtk_container_add (GTK_CONTAINER (box), image); + pixbuf = frame_pixbuf (NULL, NULL, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE); + gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf); + g_object_unref (pixbuf); entry = gtk_entry_new (); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); -- cgit v1.2.3