From be5f5ec9d40c9cad408dbd4be51947bb16cd0d48 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 6 Nov 2019 00:46:41 +0100 Subject: [PATCH] shell: Make screenshot API stream based Instead of dealing with filenames, make the low-level API use streams so the target remains generic. This so far means JS code now determines the appropriate filename to use for storing the screenshot, but will be used in other ways in future commits. https://gitlab.gnome.org/GNOME/mutter/issues/789 https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/810 --- js/ui/screenshot.js | 77 ++++++++++++++++++--- src/shell-screenshot.c | 147 ++++++----------------------------------- src/shell-screenshot.h | 9 +-- 3 files changed, 91 insertions(+), 142 deletions(-) diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index db9634364..c50c4a363 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -64,7 +64,52 @@ var ScreenshotService = class { y + height <= global.screen_height; } - _onScreenshotComplete(result, area, filenameUsed, flash, invocation) { + *_resolveRelativeFilename(filename) { + if (GLib.str_has_suffix(filename, '.png')) + filename = filename.substr(0, -4); + + let path = [ + GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES), + GLib.get_home_dir(), + ].find(p => GLib.file_test(p, GLib.FileTest.EXISTS)); + + if (!path) + return null; + + yield Gio.File.new_for_path( + GLib.build_filenamev([path, `${filename}.png`])); + + for (let idx = 1; ; idx++) { + yield Gio.File.new_for_path( + GLib.build_filenamev([path, `${filename}-${idx}.png`])); + } + } + + _createStream(filename) { + if (GLib.path_is_absolute(filename)) { + try { + let file = Gio.File.new_for_path(filename); + let stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null); + return [stream, file]; + } catch (e) { + return [null, null]; + } + } + + for (let file of this._resolveRelativeFilename(filename)) { + try { + let stream = file.create(Gio.FileCreateFlags.NONE, null); + return [stream, file]; + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS)) + return [null, null]; + } + } + + return [null, null]; + } + + _onScreenshotComplete(result, area, stream, file, flash, invocation) { if (result) { if (flash) { let flashspot = new Flashspot(area); @@ -76,6 +121,9 @@ var ScreenshotService = class { } } + stream.close(null); + + let filenameUsed = file.get_path(); let retval = GLib.Variant.new('(bs)', [result, filenameUsed]); invocation.return_value(retval); } @@ -110,13 +158,16 @@ var ScreenshotService = class { let screenshot = this._createScreenshot(invocation); if (!screenshot) return; - screenshot.screenshot_area (x, y, width, height, filename, + + let [stream, file] = this._createStream(filename); + + screenshot.screenshot_area (x, y, width, height, stream, (o, res) => { try { - let [result, area, filenameUsed] = + let [result, area] = screenshot.screenshot_area_finish(res); this._onScreenshotComplete( - result, area, filenameUsed, flash, invocation); + result, area, stream, file, flash, invocation); } catch (e) { invocation.return_gerror (e); } @@ -128,13 +179,16 @@ var ScreenshotService = class { let screenshot = this._createScreenshot(invocation); if (!screenshot) return; - screenshot.screenshot_window (includeFrame, includeCursor, filename, + + let [stream, file] = this._createStream(filename); + + screenshot.screenshot_window (includeFrame, includeCursor, stream, (o, res) => { try { - let [result, area, filenameUsed] = + let [result, area] = screenshot.screenshot_window_finish(res); this._onScreenshotComplete( - result, area, filenameUsed, flash, invocation); + result, area, stream, file, flash, invocation); } catch (e) { invocation.return_gerror (e); } @@ -146,13 +200,16 @@ var ScreenshotService = class { let screenshot = this._createScreenshot(invocation); if (!screenshot) return; - screenshot.screenshot(includeCursor, filename, + + let [stream, file] = this._createStream(filename); + + screenshot.screenshot(includeCursor, stream, (o, res) => { try { - let [result, area, filenameUsed] = + let [result, area] = screenshot.screenshot_finish(res); this._onScreenshotComplete( - result, area, filenameUsed, flash, invocation); + result, area, stream, file, flash, invocation); } catch (e) { invocation.return_gerror (e); } diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c index a7296e566..4af51c698 100644 --- a/src/shell-screenshot.c +++ b/src/shell-screenshot.c @@ -25,8 +25,7 @@ struct _ShellScreenshotPrivate { ShellGlobal *global; - char *filename; - char *filename_used; + GOutputStream *stream; GDateTime *datetime; @@ -72,102 +71,12 @@ on_screenshot_written (GObject *source, g_object_unref (result); g_clear_pointer (&priv->image, cairo_surface_destroy); - g_clear_pointer (&priv->filename, g_free); - g_clear_pointer (&priv->filename_used, g_free); + g_clear_object (&priv->stream); g_clear_pointer (&priv->datetime, g_date_time_unref); meta_enable_unredirect_for_display (shell_global_get_display (priv->global)); } -/* called in an I/O thread */ -static GOutputStream * -get_stream_for_unique_path (const gchar *path, - const gchar *filename, - gchar **filename_used) -{ - GOutputStream *stream; - GFile *file; - gchar *real_path, *real_filename, *name, *ptr; - gint idx; - - ptr = g_strrstr (filename, ".png"); - - if (ptr != NULL) - real_filename = g_strndup (filename, ptr - filename); - else - real_filename = g_strdup (filename); - - idx = 0; - real_path = NULL; - - do - { - if (idx == 0) - name = g_strdup_printf ("%s.png", real_filename); - else - name = g_strdup_printf ("%s - %d.png", real_filename, idx); - - real_path = g_build_filename (path, name, NULL); - g_free (name); - - file = g_file_new_for_path (real_path); - stream = G_OUTPUT_STREAM (g_file_create (file, G_FILE_CREATE_NONE, NULL, NULL)); - g_object_unref (file); - - if (stream != NULL) - *filename_used = real_path; - else - g_free (real_path); - - idx++; - } - while (stream == NULL); - - g_free (real_filename); - - return stream; -} - -/* called in an I/O thread */ -static GOutputStream * -get_stream_for_filename (const gchar *filename, - gchar **filename_used) -{ - const gchar *path; - - path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - { - path = g_get_home_dir (); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - return NULL; - } - - return get_stream_for_unique_path (path, filename, filename_used); -} - -static GOutputStream * -prepare_write_stream (const gchar *filename, - gchar **filename_used) -{ - GOutputStream *stream; - GFile *file; - - if (g_path_is_absolute (filename)) - { - file = g_file_new_for_path (filename); - *filename_used = g_strdup (filename); - stream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL)); - g_object_unref (file); - } - else - { - stream = get_stream_for_filename (filename, filename_used); - } - - return stream; -} - static void write_screenshot_thread (GTask *result, gpointer object, @@ -184,8 +93,7 @@ write_screenshot_thread (GTask *result, priv = screenshot->priv; - stream = prepare_write_stream (priv->filename, - &priv->filename_used); + stream = g_object_ref (priv->stream); if (stream == NULL) status = CAIRO_STATUS_FILE_NOT_FOUND; @@ -517,7 +425,6 @@ static gboolean finish_screenshot (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error) { ShellScreenshotPrivate *priv = screenshot->priv; @@ -528,9 +435,6 @@ finish_screenshot (ShellScreenshot *screenshot, if (area) *area = &priv->screenshot_area; - if (filename_used) - *filename_used = priv->filename_used; - return TRUE; } @@ -538,19 +442,19 @@ finish_screenshot (ShellScreenshot *screenshot, * shell_screenshot_screenshot: * @screenshot: the #ShellScreenshot * @include_cursor: Whether to include the cursor or not - * @filename: The filename for the screenshot + * @stream: The stream for the screenshot * @callback: (scope async): function to call returning success or failure * of the async grabbing * @user_data: the data to pass to callback function * * Takes a screenshot of the whole screen - * in @filename as png image. + * in @stream as png image. * */ void shell_screenshot_screenshot (ShellScreenshot *screenshot, gboolean include_cursor, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data) { @@ -559,7 +463,7 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot, const char *paint_signal; GTask *result; - if (priv->filename != NULL) { + if (priv->stream != NULL) { if (callback) g_task_report_new_error (screenshot, callback, @@ -575,7 +479,7 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot, result = g_task_new (screenshot, NULL, callback, user_data); g_task_set_source_tag (result, shell_screenshot_screenshot); - priv->filename = g_strdup (filename); + priv->stream = g_object_ref (stream); priv->include_cursor = FALSE; stage = CLUTTER_ACTOR (shell_global_get_stage (priv->global)); @@ -601,8 +505,6 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot, * @screenshot: the #ShellScreenshot * @result: the #GAsyncResult that was provided to the callback * @area: (out) (transfer none): the area that was grabbed in screen coordinates - * @filename_used: (out) (transfer none): the name of the file the screenshot - * was written to * @error: #GError for error reporting * * Finish the asynchronous operation started by shell_screenshot_screenshot() @@ -615,13 +517,12 @@ gboolean shell_screenshot_screenshot_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error) { g_return_val_if_fail (g_async_result_is_tagged (result, shell_screenshot_screenshot), FALSE); - return finish_screenshot (screenshot, result, area, filename_used, error); + return finish_screenshot (screenshot, result, area, error); } /** @@ -631,13 +532,13 @@ shell_screenshot_screenshot_finish (ShellScreenshot *screenshot, * @y: The Y coordinate of the area * @width: The width of the area * @height: The height of the area - * @filename: The filename for the screenshot + * @stream: The stream for the screenshot * @callback: (scope async): function to call returning success or failure * of the async grabbing * @user_data: the data to pass to callback function * * Takes a screenshot of the passed in area and saves it - * in @filename as png image. + * in @stream as png image. * */ void @@ -646,7 +547,7 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, int y, int width, int height, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data) { @@ -654,7 +555,7 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, ShellScreenshotPrivate *priv = screenshot->priv; GTask *result; - if (priv->filename != NULL) { + if (priv->stream != NULL) { if (callback) g_task_report_new_error (screenshot, callback, @@ -670,7 +571,7 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, result = g_task_new (screenshot, NULL, callback, user_data); g_task_set_source_tag (result, shell_screenshot_screenshot_area); - priv->filename = g_strdup (filename); + priv->stream = g_object_ref (stream); priv->screenshot_area.x = x; priv->screenshot_area.y = y; priv->screenshot_area.width = width; @@ -690,8 +591,6 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, * @screenshot: the #ShellScreenshot * @result: the #GAsyncResult that was provided to the callback * @area: (out) (transfer none): the area that was grabbed in screen coordinates - * @filename_used: (out) (transfer none): the name of the file the screenshot - * was written to * @error: #GError for error reporting * * Finish the asynchronous operation started by shell_screenshot_screenshot_area() @@ -704,13 +603,12 @@ gboolean shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error) { g_return_val_if_fail (g_async_result_is_tagged (result, shell_screenshot_screenshot_area), FALSE); - return finish_screenshot (screenshot, result, area, filename_used, error); + return finish_screenshot (screenshot, result, area, error); } /** @@ -718,20 +616,20 @@ shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot, * @screenshot: the #ShellScreenshot * @include_frame: Whether to include the frame or not * @include_cursor: Whether to include the cursor or not - * @filename: The filename for the screenshot + * @stream: The stream for the screenshot * @callback: (scope async): function to call returning success or failure * of the async grabbing * @user_data: the data to pass to callback function * * Takes a screenshot of the focused window (optionally omitting the frame) - * in @filename as png image. + * in @stream as png image. * */ void shell_screenshot_screenshot_window (ShellScreenshot *screenshot, gboolean include_frame, gboolean include_cursor, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data) { @@ -741,7 +639,7 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, MetaWindow *window = meta_display_get_focus_window (display); GTask *result; - if (priv->filename != NULL || !window) { + if (priv->stream != NULL || !window) { if (callback) g_task_report_new_error (screenshot, callback, @@ -757,7 +655,7 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, result = g_task_new (screenshot, NULL, callback, user_data); g_task_set_source_tag (result, shell_screenshot_screenshot_window); - priv->filename = g_strdup (filename); + priv->stream = g_object_ref (stream); priv->include_frame = include_frame; priv->include_cursor = include_cursor && should_draw_cursor_image (SHELL_SCREENSHOT_WINDOW); @@ -776,8 +674,6 @@ shell_screenshot_screenshot_window (ShellScreenshot *screenshot, * @screenshot: the #ShellScreenshot * @result: the #GAsyncResult that was provided to the callback * @area: (out) (transfer none): the area that was grabbed in screen coordinates - * @filename_used: (out) (transfer none): the name of the file the screenshot - * was written to * @error: #GError for error reporting * * Finish the asynchronous operation started by shell_screenshot_screenshot_window() @@ -790,13 +686,12 @@ gboolean shell_screenshot_screenshot_window_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error) { g_return_val_if_fail (g_async_result_is_tagged (result, shell_screenshot_screenshot_window), FALSE); - return finish_screenshot (screenshot, result, area, filename_used, error); + return finish_screenshot (screenshot, result, area, error); } /** diff --git a/src/shell-screenshot.h b/src/shell-screenshot.h index 2367d518e..18c6667e1 100644 --- a/src/shell-screenshot.h +++ b/src/shell-screenshot.h @@ -21,36 +21,33 @@ void shell_screenshot_screenshot_area (ShellScreenshot *screenshot, int y, int width, int height, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data); gboolean shell_screenshot_screenshot_area_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error); void shell_screenshot_screenshot_window (ShellScreenshot *screenshot, gboolean include_frame, gboolean include_cursor, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data); gboolean shell_screenshot_screenshot_window_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error); void shell_screenshot_screenshot (ShellScreenshot *screenshot, gboolean include_cursor, - const char *filename, + GOutputStream *stream, GAsyncReadyCallback callback, gpointer user_data); gboolean shell_screenshot_screenshot_finish (ShellScreenshot *screenshot, GAsyncResult *result, cairo_rectangle_int_t **area, - const char **filename_used, GError **error); void shell_screenshot_pick_color (ShellScreenshot *screenshot,