From 6f0f7d1b40a8fa8d46a2d6f4317600001cdbbb18 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:57:27 +0200 Subject: Adding upstream version 43.5. Signed-off-by: Daniel Baumann --- src/gs-screenshot-image.c | 952 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 952 insertions(+) create mode 100644 src/gs-screenshot-image.c (limited to 'src/gs-screenshot-image.c') diff --git a/src/gs-screenshot-image.c b/src/gs-screenshot-image.c new file mode 100644 index 0000000..a2e3bb9 --- /dev/null +++ b/src/gs-screenshot-image.c @@ -0,0 +1,952 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013-2016 Richard Hughes + * Copyright (C) 2013 Matthias Clasen + * Copyright (C) 2014-2018 Kalev Lember + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include + +#include "gs-screenshot-image.h" +#include "gs-common.h" + +#define SPINNER_TIMEOUT_SECS 2 + +struct _GsScreenshotImage +{ + GtkWidget parent_instance; + + AsScreenshot *screenshot; + GtkWidget *spinner; + GtkWidget *stack; + GtkWidget *box_error; + GtkWidget *image1; + GtkWidget *image2; + GtkWidget *video; + GtkWidget *label_error; + GSettings *settings; + SoupSession *session; + SoupMessage *message; + GCancellable *cancellable; + gchar *filename; + const gchar *current_image; + guint width; + guint height; + guint scale; + guint load_timeout_id; + gboolean showing_image; +}; + +G_DEFINE_TYPE (GsScreenshotImage, gs_screenshot_image, GTK_TYPE_WIDGET) + +enum { + SIGNAL_CLICKED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +static void +gs_screenshot_image_clicked_cb (GtkGestureClick *gesture, + gint n_press, + gdouble x, + gdouble y, + gpointer user_data) +{ + GsScreenshotImage *self = user_data; + if (n_press == 1) + g_signal_emit (self, signals[SIGNAL_CLICKED], 0); +} + +AsScreenshot * +gs_screenshot_image_get_screenshot (GsScreenshotImage *ssimg) +{ + g_return_val_if_fail (GS_IS_SCREENSHOT_IMAGE (ssimg), NULL); + return ssimg->screenshot; +} + +static void +gs_screenshot_image_start_spinner (GsScreenshotImage *ssimg) +{ + gtk_widget_show (ssimg->spinner); + gtk_spinner_start (GTK_SPINNER (ssimg->spinner)); +} + +static void +gs_screenshot_image_stop_spinner (GsScreenshotImage *ssimg) +{ + gtk_spinner_stop (GTK_SPINNER (ssimg->spinner)); + gtk_widget_hide (ssimg->spinner); +} + +static void +gs_screenshot_image_set_error (GsScreenshotImage *ssimg, const gchar *message) +{ + gint width, height; + + gtk_stack_set_visible_child_name (GTK_STACK (ssimg->stack), "error"); + gtk_label_set_label (GTK_LABEL (ssimg->label_error), message); + gtk_widget_get_size_request (ssimg->stack, &width, &height); + if (width < 200) + gtk_widget_hide (ssimg->label_error); + else + gtk_widget_show (ssimg->label_error); + ssimg->showing_image = FALSE; + gs_screenshot_image_stop_spinner (ssimg); +} + +static void +as_screenshot_show_image (GsScreenshotImage *ssimg) +{ + if (as_screenshot_get_media_kind (ssimg->screenshot) == AS_SCREENSHOT_MEDIA_KIND_VIDEO) { + gtk_video_set_filename (GTK_VIDEO (ssimg->video), ssimg->filename); + ssimg->current_image = "video"; + } else { + g_autoptr(GdkPixbuf) pixbuf = NULL; + + /* no need to composite */ + if (ssimg->width == G_MAXUINT || ssimg->height == G_MAXUINT) { + pixbuf = gdk_pixbuf_new_from_file (ssimg->filename, NULL); + } else { + /* this is always going to have alpha */ + pixbuf = gdk_pixbuf_new_from_file_at_scale (ssimg->filename, + (gint) (ssimg->width * ssimg->scale), + (gint) (ssimg->height * ssimg->scale), + FALSE, NULL); + } + + /* show icon */ + if (g_strcmp0 (ssimg->current_image, "image1") == 0) { + if (pixbuf != NULL) + gtk_picture_set_pixbuf (GTK_PICTURE (ssimg->image2), pixbuf); + ssimg->current_image = "image2"; + } else { + if (pixbuf != NULL) + gtk_picture_set_pixbuf (GTK_PICTURE (ssimg->image1), pixbuf); + ssimg->current_image = "image1"; + } + } + + gtk_stack_set_visible_child_name (GTK_STACK (ssimg->stack), ssimg->current_image); + + gtk_widget_show (GTK_WIDGET (ssimg)); + ssimg->showing_image = TRUE; + + gs_screenshot_image_stop_spinner (ssimg); +} + +static GdkPixbuf * +gs_pixbuf_resample (GdkPixbuf *original, + guint width, + guint height, + gboolean blurred) +{ + g_autoptr(GdkPixbuf) pixbuf = NULL; + guint tmp_height; + guint tmp_width; + guint pixbuf_height; + guint pixbuf_width; + g_autoptr(GdkPixbuf) pixbuf_tmp = NULL; + + /* never set */ + if (original == NULL) + return NULL; + + /* 0 means 'default' */ + if (width == 0) + width = (guint) gdk_pixbuf_get_width (original); + if (height == 0) + height = (guint) gdk_pixbuf_get_height (original); + + /* don't do anything to an image with the correct size */ + pixbuf_width = (guint) gdk_pixbuf_get_width (original); + pixbuf_height = (guint) gdk_pixbuf_get_height (original); + if (width == pixbuf_width && height == pixbuf_height) + return g_object_ref (original); + + /* is the aspect ratio of the source perfectly 16:9 */ + if ((pixbuf_width / 16) * 9 == pixbuf_height) { + pixbuf = gdk_pixbuf_scale_simple (original, + (gint) width, (gint) height, + GDK_INTERP_HYPER); + if (blurred) + gs_utils_pixbuf_blur (pixbuf, 5, 3); + return g_steal_pointer (&pixbuf); + } + + /* create new 16:9 pixbuf with alpha padding */ + pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, 8, + (gint) width, + (gint) height); + gdk_pixbuf_fill (pixbuf, 0x00000000); + /* check the ratio to see which property needs to be fitted and which needs + * to be reduced */ + if (pixbuf_width * 9 > pixbuf_height * 16) { + tmp_width = width; + tmp_height = width * pixbuf_height / pixbuf_width; + } else { + tmp_width = height * pixbuf_width / pixbuf_height; + tmp_height = height; + } + pixbuf_tmp = gdk_pixbuf_scale_simple (original, + (gint) tmp_width, + (gint) tmp_height, + GDK_INTERP_HYPER); + if (blurred) + gs_utils_pixbuf_blur (pixbuf_tmp, 5, 3); + gdk_pixbuf_copy_area (pixbuf_tmp, + 0, 0, /* of src */ + (gint) tmp_width, + (gint) tmp_height, + pixbuf, + (gint) (width - tmp_width) / 2, + (gint) (height - tmp_height) / 2); + return g_steal_pointer (&pixbuf); +} + +static gboolean +gs_pixbuf_save_filename (GdkPixbuf *pixbuf, + const gchar *filename, + guint width, + guint height, + GError **error) +{ + g_autoptr(GdkPixbuf) pb = NULL; + + /* resample & save pixbuf */ + pb = gs_pixbuf_resample (pixbuf, width, height, FALSE); + return gdk_pixbuf_save (pb, + filename, + "png", + error, + NULL); +} + +static void +gs_screenshot_image_show_blurred (GsScreenshotImage *ssimg, + const gchar *filename_thumb) +{ + g_autoptr(GdkPixbuf) pb_src = NULL; + g_autoptr(GdkPixbuf) pb = NULL; + + pb_src = gdk_pixbuf_new_from_file (filename_thumb, NULL); + if (pb_src == NULL) + return; + pb = gs_pixbuf_resample (pb_src, + ssimg->width * ssimg->scale, + ssimg->height * ssimg->scale, + TRUE /* blurred */); + if (pb == NULL) + return; + + if (g_strcmp0 (ssimg->current_image, "video") == 0) { + ssimg->current_image = "image1"; + gtk_stack_set_visible_child_name (GTK_STACK (ssimg->stack), ssimg->current_image); + } + + if (g_strcmp0 (ssimg->current_image, "image1") == 0) { + gtk_picture_set_pixbuf (GTK_PICTURE (ssimg->image1), pb); + } else { + gtk_picture_set_pixbuf (GTK_PICTURE (ssimg->image2), pb); + } +} + +static gboolean +gs_screenshot_image_save_downloaded_img (GsScreenshotImage *ssimg, + GdkPixbuf *pixbuf, + GError **error) +{ + gboolean ret; + const GPtrArray *images; + g_autoptr(GError) error_local = NULL; + g_autofree char *filename = NULL; + g_autofree char *size_dir = NULL; + g_autofree char *cache_kind = NULL; + g_autofree char *basename = NULL; + guint width = ssimg->width; + guint height = ssimg->height; + + ret = gs_pixbuf_save_filename (pixbuf, ssimg->filename, + ssimg->width * ssimg->scale, + ssimg->height * ssimg->scale, + error); + + if (!ret) + return FALSE; + + if (ssimg->screenshot == NULL) + return TRUE; + + images = as_screenshot_get_images (ssimg->screenshot); + if (images->len > 1) + return TRUE; + + if (width == AS_IMAGE_THUMBNAIL_WIDTH && + height == AS_IMAGE_THUMBNAIL_HEIGHT) { + width = AS_IMAGE_NORMAL_WIDTH; + height = AS_IMAGE_NORMAL_HEIGHT; + } else { + width = AS_IMAGE_THUMBNAIL_WIDTH; + height = AS_IMAGE_THUMBNAIL_HEIGHT; + } + + width *= ssimg->scale; + height *= ssimg->scale; + basename = g_path_get_basename (ssimg->filename); + size_dir = g_strdup_printf ("%ux%u", width, height); + cache_kind = g_build_filename ("screenshots", size_dir, NULL); + filename = gs_utils_get_cache_filename (cache_kind, basename, + GS_UTILS_CACHE_FLAG_WRITEABLE | + GS_UTILS_CACHE_FLAG_CREATE_DIRECTORY, + &error_local); + + if (filename == NULL) { + /* if we cannot get a cache filename, warn about that but do not + * set a user's visible error because this is a complementary + * operation */ + g_warning ("Failed to get cache filename for counterpart " + "screenshot '%s' in folder '%s': %s", basename, + cache_kind, error_local->message); + return TRUE; + } + + ret = gs_pixbuf_save_filename (pixbuf, filename, + width, height, + &error_local); + + if (!ret) { + /* if we cannot save this screenshot, warn about that but do not + * set a user's visible error because this is a complementary + * operation */ + g_warning ("Failed to save screenshot '%s': %s", filename, + error_local->message); + } + + return TRUE; +} + +static void +#if SOUP_CHECK_VERSION(3, 0, 0) +gs_screenshot_image_complete_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +#else +gs_screenshot_image_complete_cb (SoupSession *session, + SoupMessage *msg, + gpointer user_data) +#endif +{ + g_autoptr(GsScreenshotImage) ssimg = GS_SCREENSHOT_IMAGE (user_data); + gboolean ret; + g_autoptr(GError) error = NULL; + g_autoptr(GdkPixbuf) pixbuf = NULL; + g_autoptr(GInputStream) stream = NULL; + guint status_code; + +#if SOUP_CHECK_VERSION(3, 0, 0) + SoupMessage *msg; + + stream = soup_session_send_finish (SOUP_SESSION (source_object), result, &error); + if (stream == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning ("Failed to download screenshot: %s", error->message); + /* Reset the width request, thus the image shrinks when the window width is small */ + gtk_widget_set_size_request (ssimg->stack, -1, (gint) ssimg->height); + gs_screenshot_image_stop_spinner (ssimg); + gs_screenshot_image_set_error (ssimg, _("Screenshot not found")); + } + return; + } + + msg = soup_session_get_async_result_message (SOUP_SESSION (source_object), result); + status_code = soup_message_get_status (msg); +#else + status_code = msg->status_code; +#endif + if (ssimg->load_timeout_id) { + g_source_remove (ssimg->load_timeout_id); + ssimg->load_timeout_id = 0; + } + + /* return immediately if the message was cancelled or if we're in destruction */ +#if SOUP_CHECK_VERSION(3, 0, 0) + if (ssimg->session == NULL) +#else + if (status_code == SOUP_STATUS_CANCELLED || ssimg->session == NULL) +#endif + return; + + /* Reset the width request, thus the image shrinks when the window width is small */ + gtk_widget_set_size_request (ssimg->stack, -1, (gint) ssimg->height); + + if (status_code == SOUP_STATUS_NOT_MODIFIED) { + g_debug ("screenshot has not been modified"); + as_screenshot_show_image (ssimg); + gs_screenshot_image_stop_spinner (ssimg); + return; + } + if (status_code != SOUP_STATUS_OK) { + /* Ignore failures due to being offline */ +#if SOUP_CHECK_VERSION(3, 0, 0) + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE)) { +#else + if (status_code != SOUP_STATUS_CANT_RESOLVE) { +#endif + const gchar *reason_phrase; +#if SOUP_CHECK_VERSION(3, 0, 0) + reason_phrase = soup_message_get_reason_phrase (msg); +#else + reason_phrase = msg->reason_phrase; +#endif + g_warning ("Result of screenshot downloading attempt with " + "status code '%u': %s", status_code, + reason_phrase); + } + gs_screenshot_image_stop_spinner (ssimg); + /* if we're already showing an image, then don't set the error + * as having an image (even if outdated) is better */ + if (ssimg->showing_image) + return; + /* TRANSLATORS: this is when we try to download a screenshot and + * we get back 404 */ + gs_screenshot_image_set_error (ssimg, _("Screenshot not found")); + return; + } + +#if !SOUP_CHECK_VERSION(3, 0, 0) + /* create a buffer with the data */ + stream = g_memory_input_stream_new_from_data (msg->response_body->data, + msg->response_body->length, + NULL); + if (stream == NULL) { + gs_screenshot_image_stop_spinner (ssimg); + return; + } +#endif + + /* load the image */ + pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL); + if (pixbuf == NULL) { + /* TRANSLATORS: possibly image file corrupt or not an image */ + gs_screenshot_image_set_error (ssimg, _("Failed to load image")); + return; + } + + /* is image size destination size unknown or exactly the correct size */ + if (ssimg->width == G_MAXUINT || ssimg->height == G_MAXUINT || + (ssimg->width * ssimg->scale == (guint) gdk_pixbuf_get_width (pixbuf) && + ssimg->height * ssimg->scale == (guint) gdk_pixbuf_get_height (pixbuf))) { + ret = gs_pixbuf_save_filename (pixbuf, ssimg->filename, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + &error); + if (!ret) { + gs_screenshot_image_set_error (ssimg, error->message); + return; + } + } else if (!gs_screenshot_image_save_downloaded_img (ssimg, pixbuf, + &error)) { + gs_screenshot_image_set_error (ssimg, error->message); + return; + } + + /* got image, so show */ + as_screenshot_show_image (ssimg); +} + +void +gs_screenshot_image_set_screenshot (GsScreenshotImage *ssimg, + AsScreenshot *screenshot) +{ + g_return_if_fail (GS_IS_SCREENSHOT_IMAGE (ssimg)); + g_return_if_fail (AS_IS_SCREENSHOT (screenshot)); + + if (ssimg->screenshot == screenshot) + return; + if (ssimg->screenshot) + g_object_unref (ssimg->screenshot); + ssimg->screenshot = g_object_ref (screenshot); + + /* we reset this flag here too because it referred to the previous + * screenshot, and thus avoids potentially assuming that the new + * screenshot is shown when it is the previous one instead */ + ssimg->showing_image = FALSE; +} + +void +gs_screenshot_image_set_size (GsScreenshotImage *ssimg, + guint width, guint height) +{ + g_return_if_fail (GS_IS_SCREENSHOT_IMAGE (ssimg)); + g_return_if_fail (width != 0); + g_return_if_fail (height != 0); + + ssimg->width = width; + ssimg->height = height; + /* Reset the width request, thus the image shrinks when the window width is small */ + gtk_widget_set_size_request (ssimg->stack, -1, (gint) height); +} + +static gchar * +gs_screenshot_get_cachefn_for_url (const gchar *url) +{ + g_autofree gchar *basename = NULL; + g_autofree gchar *checksum = NULL; + checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA256, url, -1); + basename = g_path_get_basename (url); + return g_strdup_printf ("%s-%s", checksum, basename); +} + +static void +gs_screenshot_soup_msg_set_modified_request (SoupMessage *msg, GFile *file) +{ +#ifndef GLIB_VERSION_2_62 + GTimeVal time_val; +#endif + g_autoptr(GDateTime) date_time = NULL; + g_autoptr(GFileInfo) info = NULL; + g_autofree gchar *mod_date = NULL; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_TIME_MODIFIED, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + if (info == NULL) + return; +#ifdef GLIB_VERSION_2_62 + date_time = g_file_info_get_modification_date_time (info); +#else + g_file_info_get_modification_time (info, &time_val); + date_time = g_date_time_new_from_timeval_local (&time_val); +#endif + mod_date = g_date_time_format (date_time, "%a, %d %b %Y %H:%M:%S %Z"); + soup_message_headers_append ( +#if SOUP_CHECK_VERSION(3, 0, 0) + soup_message_get_request_headers (msg), +#else + msg->request_headers, +#endif + "If-Modified-Since", + mod_date); +} + +static gboolean +gs_screenshot_show_spinner_cb (gpointer user_data) +{ + GsScreenshotImage *ssimg = user_data; + + ssimg->load_timeout_id = 0; + gs_screenshot_image_start_spinner (ssimg); + + return FALSE; +} + +static const gchar * +gs_screenshot_image_get_url (GsScreenshotImage *ssimg) +{ + const gchar *url = NULL; + + /* load an image according to the scale factor */ + ssimg->scale = (guint) gtk_widget_get_scale_factor (GTK_WIDGET (ssimg)); + + if (as_screenshot_get_media_kind (ssimg->screenshot) == AS_SCREENSHOT_MEDIA_KIND_VIDEO) { + GPtrArray *videos; + AsVideo *best_video = NULL; + gint64 best_size = G_MAXINT64; + gint64 wh = (gint64) ssimg->width * ssimg->scale * ssimg->height * ssimg->scale; + + videos = as_screenshot_get_videos (ssimg->screenshot); + for (guint i = 0; videos != NULL && i < videos->len; i++) { + AsVideo *adept = g_ptr_array_index (videos, i); + gint64 tmp; + + tmp = ABS (wh - (gint64) (as_video_get_width (adept) * as_video_get_height (adept))); + if (tmp < best_size) { + best_size = tmp; + best_video = adept; + if (!tmp) + break; + } + } + + if (best_video) + url = as_video_get_url (best_video); + } else if (as_screenshot_get_media_kind (ssimg->screenshot) == AS_SCREENSHOT_MEDIA_KIND_IMAGE) { + AsImage *im; + + im = as_screenshot_get_image (ssimg->screenshot, + ssimg->width * ssimg->scale, + ssimg->height * ssimg->scale); + + /* if we've failed to load a HiDPI image, fallback to LoDPI */ + if (im == NULL && ssimg->scale > 1) { + ssimg->scale = 1; + im = as_screenshot_get_image (ssimg->screenshot, + ssimg->width, + ssimg->height); + } + + if (im) + url = as_image_get_url (im); + } + + return url; +} + +static void +gs_screenshot_video_downloaded_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GsScreenshotImage) ssimg = user_data; + g_autoptr(GError) error = NULL; + + if (gs_download_file_finish (ssimg->session, result, &error) || + g_error_matches (error, GS_DOWNLOAD_ERROR, GS_DOWNLOAD_ERROR_NOT_MODIFIED)) { + gs_screenshot_image_stop_spinner (ssimg); + as_screenshot_show_image (ssimg); + + g_clear_object (&ssimg->cancellable); + } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_debug ("Failed to download screenshot video: %s", error->message); + /* Reset the width request, thus the image shrinks when the window width is small */ + gtk_widget_set_size_request (ssimg->stack, -1, (gint) ssimg->height); + gs_screenshot_image_stop_spinner (ssimg); + gs_screenshot_image_set_error (ssimg, _("Screenshot not found")); + } +} + +void +gs_screenshot_image_load_async (GsScreenshotImage *ssimg, + GCancellable *cancellable) +{ + const gchar *url; + g_autofree gchar *basename = NULL; + g_autofree gchar *cache_kind = NULL; + g_autofree gchar *cachefn_thumb = NULL; + g_autofree gchar *sizedir = NULL; + g_autoptr(GUri) base_uri = NULL; + + g_return_if_fail (GS_IS_SCREENSHOT_IMAGE (ssimg)); + + g_return_if_fail (AS_IS_SCREENSHOT (ssimg->screenshot)); + g_return_if_fail (ssimg->width != 0); + g_return_if_fail (ssimg->height != 0); + + /* Reset the width request, thus the image shrinks when the window width is small */ + gtk_widget_set_size_request (ssimg->stack, -1, (gint) ssimg->height); + + url = gs_screenshot_image_get_url (ssimg); + if (url == NULL) { + /* TRANSLATORS: this is when we request a screenshot size that + * the generator did not create or the parser did not add */ + gs_screenshot_image_set_error (ssimg, _("Screenshot size not found")); + return; + } + + /* check if the URL points to a local file */ + if (g_str_has_prefix (url, "file://")) { + g_free (ssimg->filename); + ssimg->filename = g_strdup (url + 7); + if (g_file_test (ssimg->filename, G_FILE_TEST_EXISTS)) { + as_screenshot_show_image (ssimg); + return; + } + } + + basename = gs_screenshot_get_cachefn_for_url (url); + if (ssimg->width == G_MAXUINT || ssimg->height == G_MAXUINT) { + sizedir = g_strdup ("unknown"); + } else { + sizedir = g_strdup_printf ("%ux%u", ssimg->width * ssimg->scale, ssimg->height * ssimg->scale); + } + cache_kind = g_build_filename ("screenshots", sizedir, NULL); + g_free (ssimg->filename); + ssimg->filename = gs_utils_get_cache_filename (cache_kind, + basename, + GS_UTILS_CACHE_FLAG_NONE, + NULL); + g_assert (ssimg->filename != NULL); + + /* does local file already exist and has recently been downloaded */ + if (g_file_test (ssimg->filename, G_FILE_TEST_EXISTS)) { + guint64 age_max; + g_autoptr(GFile) file = NULL; + + /* show the image we have in cache while we're checking for the + * new screenshot (which probably won't have changed) */ + as_screenshot_show_image (ssimg); + + /* verify the cache age against the maximum allowed */ + age_max = g_settings_get_uint (ssimg->settings, + "screenshot-cache-age-maximum"); + file = g_file_new_for_path (ssimg->filename); + /* image new enough, not re-requesting from server */ + if (age_max > 0 && gs_utils_get_file_age (file) < age_max) + return; + } + + /* if we're not showing a full-size image, we try loading a blurred + * smaller version of it straight away */ + if (!ssimg->showing_image && + as_screenshot_get_media_kind (ssimg->screenshot) == AS_SCREENSHOT_MEDIA_KIND_IMAGE && + ssimg->width > AS_IMAGE_THUMBNAIL_WIDTH && + ssimg->height > AS_IMAGE_THUMBNAIL_HEIGHT) { + const gchar *url_thumb; + g_autofree gchar *basename_thumb = NULL; + g_autofree gchar *cache_kind_thumb = NULL; + AsImage *im; + im = as_screenshot_get_image (ssimg->screenshot, + AS_IMAGE_THUMBNAIL_WIDTH * ssimg->scale, + AS_IMAGE_THUMBNAIL_HEIGHT * ssimg->scale); + url_thumb = as_image_get_url (im); + basename_thumb = gs_screenshot_get_cachefn_for_url (url_thumb); + cache_kind_thumb = g_build_filename ("screenshots", "112x63", NULL); + cachefn_thumb = gs_utils_get_cache_filename (cache_kind_thumb, + basename_thumb, + GS_UTILS_CACHE_FLAG_NONE, + NULL); + g_assert (cachefn_thumb != NULL); + if (g_file_test (cachefn_thumb, G_FILE_TEST_EXISTS)) + gs_screenshot_image_show_blurred (ssimg, cachefn_thumb); + } + + /* re-request the cache filename, which might be different as it needs + * to be writable this time */ + g_free (ssimg->filename); + ssimg->filename = gs_utils_get_cache_filename (cache_kind, + basename, + GS_UTILS_CACHE_FLAG_WRITEABLE | + GS_UTILS_CACHE_FLAG_CREATE_DIRECTORY, + NULL); + if (ssimg->filename == NULL) { + /* TRANSLATORS: this is when we try create the cache directory + * but we were out of space or permission was denied */ + gs_screenshot_image_set_error (ssimg, _("Could not create cache")); + return; + } + + /* download file */ + g_debug ("downloading %s to %s", url, ssimg->filename); + base_uri = g_uri_parse (url, SOUP_HTTP_URI_FLAGS, NULL); + if (base_uri == NULL || + (g_strcmp0 (g_uri_get_scheme (base_uri), "http") != 0 && + g_strcmp0 (g_uri_get_scheme (base_uri), "https") != 0) || + g_uri_get_host (base_uri) == NULL || + g_uri_get_path (base_uri) == NULL) { + /* TRANSLATORS: this is when we try to download a screenshot + * that was not a valid URL */ + gs_screenshot_image_set_error (ssimg, _("Screenshot not valid")); + return; + } + + if (ssimg->load_timeout_id) { + g_source_remove (ssimg->load_timeout_id); + ssimg->load_timeout_id = 0; + } + + /* cancel any previous messages */ + if (ssimg->cancellable != NULL) { + g_cancellable_cancel (ssimg->cancellable); + g_clear_object (&ssimg->cancellable); + } + + if (ssimg->message != NULL) { +#if !SOUP_CHECK_VERSION(3, 0, 0) + soup_session_cancel_message (ssimg->session, + ssimg->message, + SOUP_STATUS_CANCELLED); +#endif + g_clear_object (&ssimg->message); + } + + if (as_screenshot_get_media_kind (ssimg->screenshot) == AS_SCREENSHOT_MEDIA_KIND_VIDEO) { + g_autofree gchar *uri_str = g_uri_to_string (base_uri); + g_autoptr(GFile) output_file = NULL; + + ssimg->cancellable = g_cancellable_new (); + output_file = g_file_new_for_path (ssimg->filename); + + /* Make sure the spinner takes approximately the size the screenshot will use */ + gtk_widget_set_size_request (ssimg->stack, (gint) ssimg->width, (gint) ssimg->height); + + gs_download_file_async (ssimg->session, uri_str, output_file, G_PRIORITY_DEFAULT, NULL, NULL, + ssimg->cancellable, gs_screenshot_video_downloaded_cb, g_object_ref (ssimg)); + + return; + } + +#if SOUP_CHECK_VERSION(3, 0, 0) + ssimg->message = soup_message_new_from_uri (SOUP_METHOD_GET, base_uri); +#else + { + g_autofree gchar *uri_str = g_uri_to_string (base_uri); + ssimg->message = soup_message_new (SOUP_METHOD_GET, uri_str); + } +#endif + if (ssimg->message == NULL) { + /* TRANSLATORS: this is when networking is not available */ + gs_screenshot_image_set_error (ssimg, _("Screenshot not available")); + return; + } + + /* not all servers support If-Modified-Since, but worst case we just + * re-download the entire file again every 30 days */ + if (g_file_test (ssimg->filename, G_FILE_TEST_EXISTS)) { + g_autoptr(GFile) file = g_file_new_for_path (ssimg->filename); + gs_screenshot_soup_msg_set_modified_request (ssimg->message, file); + } + + ssimg->load_timeout_id = g_timeout_add_seconds (SPINNER_TIMEOUT_SECS, + gs_screenshot_show_spinner_cb, ssimg); + + /* send async */ +#if SOUP_CHECK_VERSION(3, 0, 0) + ssimg->cancellable = g_cancellable_new (); + soup_session_send_async (ssimg->session, ssimg->message, G_PRIORITY_DEFAULT, ssimg->cancellable, + gs_screenshot_image_complete_cb, g_object_ref (ssimg)); +#else + soup_session_queue_message (ssimg->session, + g_object_ref (ssimg->message) /* transfer full */, + gs_screenshot_image_complete_cb, + g_object_ref (ssimg)); +#endif +} + +gboolean +gs_screenshot_image_is_showing (GsScreenshotImage *ssimg) +{ + return ssimg->showing_image; +} + +void +gs_screenshot_image_set_description (GsScreenshotImage *ssimg, + const gchar *description) +{ + gtk_accessible_update_property (GTK_ACCESSIBLE (ssimg->image1), + GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, description, + -1); + gtk_accessible_update_property (GTK_ACCESSIBLE (ssimg->image2), + GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, description, + -1); +} + +static void +gs_screenshot_image_dispose (GObject *object) +{ + GsScreenshotImage *ssimg = GS_SCREENSHOT_IMAGE (object); + + if (ssimg->load_timeout_id) { + g_source_remove (ssimg->load_timeout_id); + ssimg->load_timeout_id = 0; + } + + if (ssimg->cancellable != NULL) { + g_cancellable_cancel (ssimg->cancellable); + g_clear_object (&ssimg->cancellable); + } + + if (ssimg->message != NULL) { +#if !SOUP_CHECK_VERSION(3, 0, 0) + soup_session_cancel_message (ssimg->session, + ssimg->message, + SOUP_STATUS_CANCELLED); +#endif + g_clear_object (&ssimg->message); + } + gs_widget_remove_all (GTK_WIDGET (ssimg), NULL); + g_clear_object (&ssimg->screenshot); + g_clear_object (&ssimg->session); + g_clear_object (&ssimg->settings); + + g_clear_pointer (&ssimg->filename, g_free); + + G_OBJECT_CLASS (gs_screenshot_image_parent_class)->dispose (object); +} + +static void +gs_screenshot_image_init (GsScreenshotImage *ssimg) +{ + GtkGesture *gesture; + + ssimg->settings = g_settings_new ("org.gnome.software"); + ssimg->showing_image = FALSE; + + gtk_widget_init_template (GTK_WIDGET (ssimg)); + + gesture = gtk_gesture_click_new (); + g_signal_connect_object (gesture, "released", + G_CALLBACK (gs_screenshot_image_clicked_cb), ssimg, 0); + gtk_widget_add_controller (GTK_WIDGET (ssimg), GTK_EVENT_CONTROLLER (gesture)); +} + +static void +gs_screenshot_image_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkStyleContext *context; + + context = gtk_widget_get_style_context (widget); + gtk_snapshot_render_frame (snapshot, + context, + 0.0, 0.0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget)); + + GTK_WIDGET_CLASS (gs_screenshot_image_parent_class)->snapshot (widget, snapshot); +} + +static void +gs_screenshot_image_class_init (GsScreenshotImageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gs_screenshot_image_dispose; + + widget_class->snapshot = gs_screenshot_image_snapshot; + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Software/gs-screenshot-image.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_IMG); + + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, spinner); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, stack); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, image1); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, image2); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, video); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, box_error); + gtk_widget_class_bind_template_child (widget_class, GsScreenshotImage, label_error); + + /** + * GsScreenshotImage::clicked: + * + * Emitted when the screenshot is clicked. + * + * Since: 43 + */ + signals [SIGNAL_CLICKED] = + g_signal_new ("clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GtkWidget * +gs_screenshot_image_new (SoupSession *session) +{ + GsScreenshotImage *ssimg; + ssimg = g_object_new (GS_TYPE_SCREENSHOT_IMAGE, NULL); + ssimg->session = g_object_ref (session); + return GTK_WIDGET (ssimg); +} -- cgit v1.2.3