[gnome-shell] screenshot-ui: Add capturing and screen selection
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] screenshot-ui: Add capturing and screen selection
- Date: Thu, 27 Jan 2022 22:30:53 +0000 (UTC)
commit 6f42eaf17d9cb2d0f6c21e6cdcd5a96b750f8faa
Author: Ivan Molodetskikh <yalterz gmail com>
Date: Sat Jan 15 18:23:32 2022 +0300
screenshot-ui: Add capturing and screen selection
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1954>
.../gnome-shell-sass/widgets/_screenshot.scss | 49 +++++
js/ui/screenshot.js | 223 ++++++++++++++++++++-
2 files changed, 270 insertions(+), 2 deletions(-)
---
diff --git a/data/theme/gnome-shell-sass/widgets/_screenshot.scss
b/data/theme/gnome-shell-sass/widgets/_screenshot.scss
index 5c98fce299..1c46146fa8 100644
--- a/data/theme/gnome-shell-sass/widgets/_screenshot.scss
+++ b/data/theme/gnome-shell-sass/widgets/_screenshot.scss
@@ -32,3 +32,52 @@
icon-size: $base_icon_size * 2;
}
}
+
+.screenshot-ui-type-button {
+ padding: $base_padding * 2 $base_padding * 3;
+ border-radius: 12px + 21px - 18px;
+ font-weight: bold;
+ &:hover, &:focus { background-color: $hover_bg_color; }
+ &:active { background-color: $active_bg_color; }
+ &:checked { background-color: $hover_bg_color; }
+ &:insensitive { color: $insensitive_fg_color; }
+}
+
+.screenshot-ui-capture-button {
+ width: 36px;
+ height: 36px;
+ border-radius: 99px;
+ border: 4px white;
+ padding: 4px;
+
+ .screenshot-ui-capture-button-circle {
+ background-color: white;
+ transition-duration: 200ms;
+ &:hover, &:focus { background-color: $hover_bg_color; }
+ border-radius: 99px;
+ }
+
+ &:hover, &:focus {
+ .screenshot-ui-capture-button-circle {
+ background-color: darken(white, 15%);
+ }
+ }
+
+ &:active {
+ .screenshot-ui-capture-button-circle {
+ background-color: darken(white, 50%);
+ }
+ }
+}
+
+.screenshot-ui-screen-selector {
+ transition-duration: 200ms;
+ background-color: rgba(0, 0, 0, .5);
+
+ &:hover { background-color: rgba(0, 0, 0, .3); }
+ &:active { background-color: rgba(0, 0, 0, .7); }
+ &:checked {
+ background-color: transparent;
+ border: 2px white;
+ }
+}
diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js
index 17e61531ec..364541ae4b 100644
--- a/js/ui/screenshot.js
+++ b/js/ui/screenshot.js
@@ -14,6 +14,11 @@ Gio._promisify(Shell.Screenshot.prototype,
'screenshot_window', 'screenshot_window_finish');
Gio._promisify(Shell.Screenshot.prototype,
'screenshot_area', 'screenshot_area_finish');
+Gio._promisify(Shell.Screenshot.prototype,
+ 'screenshot_stage_to_content', 'screenshot_stage_to_content_finish');
+Gio._promisify(
+ Shell.Screenshot,
+ 'composite_to_stream', 'composite_to_stream_finish');
const { loadInterfaceXML } = imports.misc.fileUtils;
const { DBusSenderChecker } = imports.misc.util;
@@ -53,8 +58,26 @@ class ScreenshotUI extends St.Widget {
visible: false,
});
+ // The full-screen screenshot has a separate container so that we can
+ // show it without the screenshot UI fade-in for a nicer animation.
+ this._stageScreenshotContainer = new St.Widget({ visible: false });
+ this._stageScreenshotContainer.add_constraint(new Clutter.BindConstraint({
+ source: global.stage,
+ coordinate: Clutter.BindCoordinate.ALL,
+ }));
+ Main.layoutManager.screenshotUIGroup.add_child(
+ this._stageScreenshotContainer);
+
Main.layoutManager.screenshotUIGroup.add_child(this);
+ this._stageScreenshot = new St.Widget({ style_class: 'screenshot-ui-screen-screenshot' });
+ this._stageScreenshot.add_constraint(new Clutter.BindConstraint({
+ source: global.stage,
+ coordinate: Clutter.BindCoordinate.ALL,
+ }));
+ this._stageScreenshotContainer.add_child(this._stageScreenshot);
+
+ this._openingCoroutineInProgress = false;
this._grabHelper = new GrabHelper.GrabHelper(this, {
actionMode: Shell.ActionMode.POPUP,
});
@@ -83,9 +106,42 @@ class ScreenshotUI extends St.Widget {
this._closeButton.connect('clicked', () => this.close());
this._primaryMonitorBin.add_child(this._closeButton);
+ this._typeButtonContainer = new St.Widget({
+ style_class: 'screenshot-ui-type-button-container',
+ layout_manager: new Clutter.BoxLayout({
+ spacing: 12,
+ homogeneous: true,
+ }),
+ });
+ this._panel.add_child(this._typeButtonContainer);
+
+ this._screenButton = new IconLabelButton('video-display-symbolic', _('Screen'), {
+ style_class: 'screenshot-ui-type-button',
+ checked: true,
+ x_expand: true,
+ });
+ this._screenButton.connect('notify::checked',
+ this._onScreenButtonToggled.bind(this));
+ this._typeButtonContainer.add_child(this._screenButton);
+
+ this._bottomRowContainer = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+ this._panel.add_child(this._bottomRowContainer);
+
+ this._captureButton = new St.Button({ style_class: 'screenshot-ui-capture-button' });
+ this._captureButton.set_child(new St.Widget({
+ style_class: 'screenshot-ui-capture-button-circle',
+ }));
+ this._captureButton.connect('clicked',
+ this._onCaptureButtonClicked.bind(this));
+ this._bottomRowContainer.add_child(this._captureButton);
+
+ this._monitorBins = [];
+ this._rebuildMonitorBins();
+
Main.layoutManager.connect('monitors-changed', () => {
// Nope, not dealing with monitor changes.
this.close(true);
+ this._rebuildMonitorBins();
});
Main.wm.addKeybinding(
@@ -101,7 +157,80 @@ class ScreenshotUI extends St.Widget {
);
}
- open() {
+ _rebuildMonitorBins() {
+ for (const bin of this._monitorBins)
+ bin.destroy();
+
+ this._monitorBins = [];
+ this._screenSelectors = [];
+
+ for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+ const bin = new St.Widget({
+ layout_manager: new Clutter.BinLayout(),
+ });
+ bin.add_constraint(new Layout.MonitorConstraint({ 'index': i }));
+ this.insert_child_below(bin, this._primaryMonitorBin);
+ this._monitorBins.push(bin);
+
+ const screenSelector = new St.Button({
+ style_class: 'screenshot-ui-screen-selector',
+ x_expand: true,
+ y_expand: true,
+ visible: this._screenButton.checked,
+ reactive: true,
+ can_focus: true,
+ toggle_mode: true,
+ });
+ screenSelector.connect('key-focus-in', () => {
+ this.grab_key_focus();
+ screenSelector.checked = true;
+ });
+ bin.add_child(screenSelector);
+ this._screenSelectors.push(screenSelector);
+
+ screenSelector.connect('notify::checked', () => {
+ if (!screenSelector.checked)
+ return;
+
+ screenSelector.toggle_mode = false;
+
+ for (const otherSelector of this._screenSelectors) {
+ if (screenSelector === otherSelector)
+ continue;
+
+ otherSelector.toggle_mode = true;
+ otherSelector.checked = false;
+ }
+ });
+ }
+
+ if (Main.layoutManager.primaryIndex !== -1)
+ this._screenSelectors[Main.layoutManager.primaryIndex].checked = true;
+ }
+
+ async open() {
+ if (this._openingCoroutineInProgress)
+ return;
+
+ if (!this.visible) {
+ // Screenshot UI is opening from completely closed state
+ // (rather than opening back from in process of closing).
+ this._shooter = new Shell.Screenshot();
+
+ this._openingCoroutineInProgress = true;
+ try {
+ const [content, scale] =
+ await this._shooter.screenshot_stage_to_content();
+ this._stageScreenshot.set_content(content);
+ this._scale = scale;
+
+ this._stageScreenshotContainer.show();
+ } catch (e) {
+ log('Error capturing screenshot: %s'.format(e.message));
+ }
+ this._openingCoroutineInProgress = false;
+ }
+
// Get rid of any popup menus.
// We already have them captured on the screenshot anyway.
//
@@ -122,11 +251,26 @@ class ScreenshotUI extends St.Widget {
opacity: 255,
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => {
+ this._stageScreenshotContainer.get_parent().remove_child(
+ this._stageScreenshotContainer);
+ this.insert_child_at_index(this._stageScreenshotContainer, 0);
+ },
});
}
_finishClosing() {
this.hide();
+
+ this._shooter = null;
+
+ this._stageScreenshotContainer.get_parent().remove_child(
+ this._stageScreenshotContainer);
+ Main.layoutManager.screenshotUIGroup.insert_child_at_index(
+ this._stageScreenshotContainer, 0);
+ this._stageScreenshotContainer.hide();
+
+ this._stageScreenshot.set_content(null);
}
close(instantly = false) {
@@ -145,13 +289,88 @@ class ScreenshotUI extends St.Widget {
onComplete: this._finishClosing.bind(this),
});
}
+
+ _onScreenButtonToggled() {
+ if (this._screenButton.checked) {
+ this._screenButton.toggle_mode = false;
+
+ for (const selector of this._screenSelectors) {
+ selector.show();
+ selector.remove_all_transitions();
+ selector.ease({
+ opacity: 255,
+ duration: 200,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ });
+ }
+ } else {
+ this._screenButton.toggle_mode = true;
+
+ for (const selector of this._screenSelectors) {
+ selector.remove_all_transitions();
+ selector.ease({
+ opacity: 0,
+ duration: 200,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => selector.hide(),
+ });
+ }
+ }
+ }
+
+ _onCaptureButtonClicked() {
+ global.display.get_sound_player().play_from_theme(
+ 'screen-capture', _('Screenshot taken'), null);
+
+ if (this._screenButton.checked) {
+ const content = this._stageScreenshot.get_content();
+ if (!content) {
+ // Failed to capture the screenshot for some reason.
+ this.close();
+ return;
+ }
+
+ const texture = content.get_texture();
+ const stream = Gio.MemoryOutputStream.new_resizable();
+
+ const index =
+ this._screenSelectors.findIndex(screen => screen.checked);
+ const monitor = Main.layoutManager.monitors[index];
+
+ const x = monitor.x * this._scale;
+ const y = monitor.y * this._scale;
+ const w = monitor.width * this._scale;
+ const h = monitor.height * this._scale;
+
+ Shell.Screenshot.composite_to_stream(
+ texture,
+ x, y, w, h,
+ stream
+ ).then(() => {
+ stream.close(null);
+
+ const clipboard = St.Clipboard.get_default();
+ clipboard.set_content(
+ St.ClipboardType.CLIPBOARD,
+ 'image/png',
+ stream.steal_as_bytes()
+ );
+ }).catch(err => {
+ logError(err, 'Error capturing screenshot');
+ });
+ }
+
+ this.close();
+ }
});
/**
* Shows the screenshot UI.
*/
function showScreenshotUI() {
- Main.screenshotUI.open();
+ Main.screenshotUI.open().catch(err => {
+ logError(err, 'Error opening the screenshot UI');
+ });
}
var ScreenshotService = class {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]