From c1bfdd74d8f03975994fedfb47cdcec99c854ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= Date: Tue, 9 Feb 2021 12:51:33 +0100 Subject: [PATCH] screenshot: Fix slow audiovisual feedback on when taking screenshot Add a "screenshot-taken" signal from the screenshot service's internal C implementation, and use that to trigger the camera flash visual effect and the click sound, allowing them to run in parallel with the PNG compression instead of waiting until the file is complete to start. This significantly improves perceived latency on high res setups such as 4K, 5K, or dual 4K screens. Fixes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/512 Co-authored-by: Brion Vibber Part-of: --- js/ui/screenshot.js | 47 +++++++++++++++++++++++++++--------------- po/POTFILES.in | 1 + src/shell-screenshot.c | 30 ++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js index 1c724f0ba..59a6c46af 100644 --- a/js/ui/screenshot.js +++ b/js/ui/screenshot.js @@ -131,16 +131,19 @@ var ScreenshotService = class { return [null, null]; } - _onScreenshotComplete(area, stream, file, flash, invocation) { - if (flash) { - let flashspot = new Flashspot(area); - flashspot.fire(() => { - this._removeShooterForSender(invocation.get_sender()); - }); - } else { - this._removeShooterForSender(invocation.get_sender()); - } + _flashAsync(shooter) { + return new Promise((resolve, _reject) => { + shooter.connect('screenshot_taken', (s, area) => { + const flashspot = new Flashspot(area); + flashspot.fire(resolve); + global.display.get_sound_player().play_from_theme( + 'screen-capture', _('Screenshot taken'), null); + }); + }); + } + + _onScreenshotComplete(stream, file, invocation) { stream.close(null); let filenameUsed = ''; @@ -192,9 +195,12 @@ var ScreenshotService = class { return; try { - let [area] = - await screenshot.screenshot_area(x, y, width, height, stream); - this._onScreenshotComplete(area, stream, file, flash, invocation); + await Promise.all([ + flash ? this._flashAsync(screenshot) : null, + screenshot.screenshot_area(x, y, width, height, stream), + ]); + this._onScreenshotComplete(stream, file, invocation); + this._removeShooterForSender(invocation.get_sender()); } catch (e) { this._removeShooterForSender(invocation.get_sender()); invocation.return_value(new GLib.Variant('(bs)', [false, ''])); @@ -212,9 +218,12 @@ var ScreenshotService = class { return; try { - let [area] = - await screenshot.screenshot_window(includeFrame, includeCursor, stream); - this._onScreenshotComplete(area, stream, file, flash, invocation); + await Promise.all([ + flash ? this._flashAsync(screenshot) : null, + screenshot.screenshot_window(includeFrame, includeCursor, stream), + ]); + this._onScreenshotComplete(stream, file, invocation); + this._removeShooterForSender(invocation.get_sender()); } catch (e) { this._removeShooterForSender(invocation.get_sender()); invocation.return_value(new GLib.Variant('(bs)', [false, ''])); @@ -232,8 +241,12 @@ var ScreenshotService = class { return; try { - let [area] = await screenshot.screenshot(includeCursor, stream); - this._onScreenshotComplete(area, stream, file, flash, invocation); + await Promise.all([ + flash ? this._flashAsync(screenshot) : null, + screenshot.screenshot(includeCursor, stream), + ]); + this._onScreenshotComplete(stream, file, invocation); + this._removeShooterForSender(invocation.get_sender()); } catch (e) { this._removeShooterForSender(invocation.get_sender()); invocation.return_value(new GLib.Variant('(bs)', [false, ''])); diff --git a/po/POTFILES.in b/po/POTFILES.in index 4f83b62cf..37f656903 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -47,6 +47,7 @@ js/ui/panel.js js/ui/popupMenu.js js/ui/runDialog.js js/ui/screenShield.js +js/ui/screenshot.js js/ui/search.js js/ui/shellEntry.js js/ui/shellMountOperation.js diff --git a/src/shell-screenshot.c b/src/shell-screenshot.c index 321a82ec7..100429f21 100644 --- a/src/shell-screenshot.c +++ b/src/shell-screenshot.c @@ -25,6 +25,15 @@ typedef enum _ShellScreenshotMode SHELL_SCREENSHOT_AREA, } ShellScreenshotMode; +enum +{ + SCREENSHOT_TAKEN, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + typedef struct _ShellScreenshotPrivate ShellScreenshotPrivate; struct _ShellScreenshot @@ -55,7 +64,15 @@ G_DEFINE_TYPE_WITH_PRIVATE (ShellScreenshot, shell_screenshot, G_TYPE_OBJECT); static void shell_screenshot_class_init (ShellScreenshotClass *screenshot_class) { - (void) screenshot_class; + signals[SCREENSHOT_TAKEN] = + g_signal_new ("screenshot-taken", + G_TYPE_FROM_CLASS(screenshot_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + META_TYPE_RECTANGLE); } static void @@ -321,6 +338,8 @@ grab_window_screenshot (ShellScreenshot *screenshot, draw_cursor_image (priv->image, priv->screenshot_area); } + g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, &rect); + task = g_task_new (screenshot, NULL, on_screenshot_written, result); g_task_run_in_thread (task, write_screenshot_thread); g_object_unref (task); @@ -372,6 +391,9 @@ on_after_paint (ClutterStage *stage, grab_screenshot (screenshot, priv->flags, result); } + g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, + (cairo_rectangle_int_t *) &priv->screenshot_area); + meta_enable_unredirect_for_display (display); } @@ -430,6 +452,9 @@ shell_screenshot_screenshot (ShellScreenshot *screenshot, if (meta_is_wayland_compositor ()) { grab_screenshot (screenshot, flags, result); + + g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, + (cairo_rectangle_int_t *) &priv->screenshot_area); } else { @@ -540,6 +565,9 @@ shell_screenshot_screenshot_area (ShellScreenshot *screenshot, priv->screenshot_area.height, SHELL_SCREENSHOT_FLAG_NONE); + g_signal_emit (screenshot, signals[SCREENSHOT_TAKEN], 0, + (cairo_rectangle_int_t *) &priv->screenshot_area); + task = g_task_new (screenshot, NULL, on_screenshot_written, result); g_task_run_in_thread (task, write_screenshot_thread); }