screenshot-ui: Add capturing and screen selection
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1954>
This commit is contained in:
parent
deb614a031
commit
6f42eaf17d
@ -32,3 +32,52 @@
|
|||||||
icon-size: $base_icon_size * 2;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,6 +14,11 @@ Gio._promisify(Shell.Screenshot.prototype,
|
|||||||
'screenshot_window', 'screenshot_window_finish');
|
'screenshot_window', 'screenshot_window_finish');
|
||||||
Gio._promisify(Shell.Screenshot.prototype,
|
Gio._promisify(Shell.Screenshot.prototype,
|
||||||
'screenshot_area', 'screenshot_area_finish');
|
'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 { loadInterfaceXML } = imports.misc.fileUtils;
|
||||||
const { DBusSenderChecker } = imports.misc.util;
|
const { DBusSenderChecker } = imports.misc.util;
|
||||||
@ -53,8 +58,26 @@ class ScreenshotUI extends St.Widget {
|
|||||||
visible: false,
|
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);
|
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, {
|
this._grabHelper = new GrabHelper.GrabHelper(this, {
|
||||||
actionMode: Shell.ActionMode.POPUP,
|
actionMode: Shell.ActionMode.POPUP,
|
||||||
});
|
});
|
||||||
@ -83,9 +106,42 @@ class ScreenshotUI extends St.Widget {
|
|||||||
this._closeButton.connect('clicked', () => this.close());
|
this._closeButton.connect('clicked', () => this.close());
|
||||||
this._primaryMonitorBin.add_child(this._closeButton);
|
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', () => {
|
Main.layoutManager.connect('monitors-changed', () => {
|
||||||
// Nope, not dealing with monitor changes.
|
// Nope, not dealing with monitor changes.
|
||||||
this.close(true);
|
this.close(true);
|
||||||
|
this._rebuildMonitorBins();
|
||||||
});
|
});
|
||||||
|
|
||||||
Main.wm.addKeybinding(
|
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.
|
// Get rid of any popup menus.
|
||||||
// We already have them captured on the screenshot anyway.
|
// We already have them captured on the screenshot anyway.
|
||||||
//
|
//
|
||||||
@ -122,11 +251,26 @@ class ScreenshotUI extends St.Widget {
|
|||||||
opacity: 255,
|
opacity: 255,
|
||||||
duration: 200,
|
duration: 200,
|
||||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => {
|
||||||
|
this._stageScreenshotContainer.get_parent().remove_child(
|
||||||
|
this._stageScreenshotContainer);
|
||||||
|
this.insert_child_at_index(this._stageScreenshotContainer, 0);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_finishClosing() {
|
_finishClosing() {
|
||||||
this.hide();
|
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) {
|
close(instantly = false) {
|
||||||
@ -145,13 +289,88 @@ class ScreenshotUI extends St.Widget {
|
|||||||
onComplete: this._finishClosing.bind(this),
|
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.
|
* Shows the screenshot UI.
|
||||||
*/
|
*/
|
||||||
function showScreenshotUI() {
|
function showScreenshotUI() {
|
||||||
Main.screenshotUI.open();
|
Main.screenshotUI.open().catch(err => {
|
||||||
|
logError(err, 'Error opening the screenshot UI');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ScreenshotService = class {
|
var ScreenshotService = class {
|
||||||
|
Loading…
Reference in New Issue
Block a user