screenshot-ui: Add window selection
UIWindowSelectorLayout is a stripped-down subclass of WorkspaceLayout (we don't have to deal with windows disappearing or appearing or changing size). UIWindowSelectorWindow is a heavily stripped-down version of WindowPreview. UIWindowSelector is analogous to the Workspace class. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1954>
This commit is contained in:
parent
f3d59912ec
commit
d10e626de9
@ -92,6 +92,41 @@
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.screenshot-ui-window-selector {
|
||||||
|
background-color: $system_bg_color;
|
||||||
|
|
||||||
|
.screenshot-ui-window-selector-window-container {
|
||||||
|
margin: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:primary-monitor {
|
||||||
|
.screenshot-ui-window-selector-window-container {
|
||||||
|
// Make some room for the panel.
|
||||||
|
margin-bottom: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenshot-ui-window-selector-window-border {
|
||||||
|
transition-duration: 200ms;
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 6px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.screenshot-ui-window-selector-window {
|
||||||
|
&:hover {
|
||||||
|
.screenshot-ui-window-selector-window-border {
|
||||||
|
border-color: darken($selected_bg_color, 15%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:checked {
|
||||||
|
.screenshot-ui-window-selector-window-border {
|
||||||
|
border-color: $selected_bg_color;
|
||||||
|
background-color: transparentize($selected_bg_color, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.screenshot-ui-screen-selector {
|
.screenshot-ui-screen-selector {
|
||||||
transition-duration: 200ms;
|
transition-duration: 200ms;
|
||||||
background-color: rgba(0, 0, 0, .5);
|
background-color: rgba(0, 0, 0, .5);
|
||||||
|
@ -7,6 +7,7 @@ const GrabHelper = imports.ui.grabHelper;
|
|||||||
const Layout = imports.ui.layout;
|
const Layout = imports.ui.layout;
|
||||||
const Lightbox = imports.ui.lightbox;
|
const Lightbox = imports.ui.lightbox;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
|
const Workspace = imports.ui.workspace;
|
||||||
|
|
||||||
Gio._promisify(Shell.Screenshot.prototype, 'pick_color', 'pick_color_finish');
|
Gio._promisify(Shell.Screenshot.prototype, 'pick_color', 'pick_color_finish');
|
||||||
Gio._promisify(Shell.Screenshot.prototype, 'screenshot', 'screenshot_finish');
|
Gio._promisify(Shell.Screenshot.prototype, 'screenshot', 'screenshot_finish');
|
||||||
@ -632,6 +633,231 @@ var UIAreaSelector = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var UIWindowSelectorLayout = GObject.registerClass(
|
||||||
|
class UIWindowSelectorLayout extends Workspace.WorkspaceLayout {
|
||||||
|
_init(monitorIndex) {
|
||||||
|
super._init(null, monitorIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfunc_set_container(container) {
|
||||||
|
this._container = container;
|
||||||
|
this._syncWorkareaTracking();
|
||||||
|
}
|
||||||
|
|
||||||
|
vfunc_allocate(container, box) {
|
||||||
|
const containerBox = container.allocation;
|
||||||
|
const containerAllocationChanged =
|
||||||
|
this._lastBox === null || !this._lastBox.equal(containerBox);
|
||||||
|
this._lastBox = containerBox.copy();
|
||||||
|
|
||||||
|
let layoutChanged = false;
|
||||||
|
if (this._layout === null) {
|
||||||
|
this._layout = this._createBestLayout(this._workarea);
|
||||||
|
layoutChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layoutChanged || containerAllocationChanged)
|
||||||
|
this._windowSlots = this._getWindowSlots(box.copy());
|
||||||
|
|
||||||
|
const childBox = new Clutter.ActorBox();
|
||||||
|
|
||||||
|
const nSlots = this._windowSlots.length;
|
||||||
|
for (let i = 0; i < nSlots; i++) {
|
||||||
|
let [x, y, width, height, child] = this._windowSlots[i];
|
||||||
|
|
||||||
|
childBox.set_origin(x, y);
|
||||||
|
childBox.set_size(width, height);
|
||||||
|
|
||||||
|
child.allocate(childBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addWindow(window) {
|
||||||
|
if (this._sortedWindows.includes(window))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._sortedWindows.push(window);
|
||||||
|
|
||||||
|
this._container.add_child(window);
|
||||||
|
|
||||||
|
this._layout = null;
|
||||||
|
this.layout_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
for (const window of this._sortedWindows)
|
||||||
|
window.destroy();
|
||||||
|
|
||||||
|
this._sortedWindows = [];
|
||||||
|
this._windowSlots = [];
|
||||||
|
this._layout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get windows() {
|
||||||
|
return this._sortedWindows;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var UIWindowSelectorWindow = GObject.registerClass(
|
||||||
|
class UIWindowSelectorWindow extends St.Button {
|
||||||
|
_init(actor, params) {
|
||||||
|
super._init(params);
|
||||||
|
|
||||||
|
const window = actor.metaWindow;
|
||||||
|
this._boundingBox = window.get_frame_rect();
|
||||||
|
this._bufferRect = window.get_buffer_rect();
|
||||||
|
this._bufferScale = actor.get_resource_scale();
|
||||||
|
this._actor = new Clutter.Actor({
|
||||||
|
content: actor.paint_to_content(null),
|
||||||
|
});
|
||||||
|
this.add_child(this._actor);
|
||||||
|
|
||||||
|
this._border = new St.Bin({ style_class: 'screenshot-ui-window-selector-window-border' });
|
||||||
|
this._border.connect('style-changed', () => {
|
||||||
|
this._borderSize =
|
||||||
|
this._border.get_theme_node().get_border_width(St.Side.TOP);
|
||||||
|
});
|
||||||
|
this.add_child(this._border);
|
||||||
|
|
||||||
|
this.connect('destroy', this._onDestroy.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
get boundingBox() {
|
||||||
|
return this._boundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
get windowCenter() {
|
||||||
|
const boundingBox = this.boundingBox;
|
||||||
|
return {
|
||||||
|
x: boundingBox.x + boundingBox.width / 2,
|
||||||
|
y: boundingBox.y + boundingBox.height / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
chromeHeights() {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
chromeWidths() {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
overlapHeights() {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get bufferScale() {
|
||||||
|
return this._bufferScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
get windowContent() {
|
||||||
|
return this._actor.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDestroy() {
|
||||||
|
this.remove_child(this._actor);
|
||||||
|
this._actor.destroy();
|
||||||
|
this._actor = null;
|
||||||
|
this.remove_child(this._border);
|
||||||
|
this._border.destroy();
|
||||||
|
this._border = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfunc_allocate(box) {
|
||||||
|
this.set_allocation(box);
|
||||||
|
|
||||||
|
// Border goes around the window.
|
||||||
|
const borderBox = box.copy();
|
||||||
|
borderBox.set_origin(0, 0);
|
||||||
|
borderBox.x1 -= this._borderSize;
|
||||||
|
borderBox.y1 -= this._borderSize;
|
||||||
|
borderBox.x2 += this._borderSize;
|
||||||
|
borderBox.y2 += this._borderSize;
|
||||||
|
this._border.allocate(borderBox);
|
||||||
|
|
||||||
|
// box should contain this._boundingBox worth of window. Compute
|
||||||
|
// origin and size for the actor box to satisfy that.
|
||||||
|
const xScale = box.get_width() / this._boundingBox.width;
|
||||||
|
const yScale = box.get_height() / this._boundingBox.height;
|
||||||
|
|
||||||
|
const [, windowW, windowH] = this._actor.content.get_preferred_size();
|
||||||
|
|
||||||
|
const actorBox = new Clutter.ActorBox();
|
||||||
|
actorBox.set_origin(
|
||||||
|
(this._bufferRect.x - this._boundingBox.x) * xScale,
|
||||||
|
(this._bufferRect.y - this._boundingBox.y) * yScale
|
||||||
|
);
|
||||||
|
actorBox.set_size(
|
||||||
|
windowW * xScale / this._bufferScale,
|
||||||
|
windowH * yScale / this._bufferScale
|
||||||
|
);
|
||||||
|
this._actor.allocate(actorBox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var UIWindowSelector = GObject.registerClass(
|
||||||
|
class UIWindowSelector extends St.Widget {
|
||||||
|
_init(monitorIndex, params) {
|
||||||
|
super._init(params);
|
||||||
|
super.layout_manager = new Clutter.BinLayout();
|
||||||
|
|
||||||
|
this._monitorIndex = monitorIndex;
|
||||||
|
|
||||||
|
this._layoutManager = new UIWindowSelectorLayout(monitorIndex);
|
||||||
|
|
||||||
|
// Window screenshots
|
||||||
|
this._container = new St.Widget({
|
||||||
|
style_class: 'screenshot-ui-window-selector-window-container',
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
});
|
||||||
|
this._container.layout_manager = this._layoutManager;
|
||||||
|
this.add_child(this._container);
|
||||||
|
}
|
||||||
|
|
||||||
|
capture() {
|
||||||
|
for (const actor of global.get_window_actors()) {
|
||||||
|
let window = actor.metaWindow;
|
||||||
|
let workspaceManager = global.workspace_manager;
|
||||||
|
let activeWorkspace = workspaceManager.get_active_workspace();
|
||||||
|
if (window.is_override_redirect() ||
|
||||||
|
!window.located_on_workspace(activeWorkspace) ||
|
||||||
|
window.get_monitor() !== this._monitorIndex)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const widget = new UIWindowSelectorWindow(
|
||||||
|
actor,
|
||||||
|
{
|
||||||
|
style_class: 'screenshot-ui-window-selector-window',
|
||||||
|
reactive: true,
|
||||||
|
can_focus: true,
|
||||||
|
toggle_mode: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
widget.connect('key-focus-in', win => {
|
||||||
|
Main.screenshotUI.grab_key_focus();
|
||||||
|
win.checked = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.has_focus()) {
|
||||||
|
widget.checked = true;
|
||||||
|
widget.toggle_mode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._layoutManager.addWindow(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this._layoutManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
windows() {
|
||||||
|
return this._layoutManager.windows;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var ScreenshotUI = GObject.registerClass(
|
var ScreenshotUI = GObject.registerClass(
|
||||||
class ScreenshotUI extends St.Widget {
|
class ScreenshotUI extends St.Widget {
|
||||||
_init() {
|
_init() {
|
||||||
@ -754,6 +980,15 @@ class ScreenshotUI extends St.Widget {
|
|||||||
this._onScreenButtonToggled.bind(this));
|
this._onScreenButtonToggled.bind(this));
|
||||||
this._typeButtonContainer.add_child(this._screenButton);
|
this._typeButtonContainer.add_child(this._screenButton);
|
||||||
|
|
||||||
|
this._windowButton = new IconLabelButton('focus-windows-symbolic', _('Window'), {
|
||||||
|
style_class: 'screenshot-ui-type-button',
|
||||||
|
toggle_mode: true,
|
||||||
|
x_expand: true,
|
||||||
|
});
|
||||||
|
this._windowButton.connect('notify::checked',
|
||||||
|
this._onWindowButtonToggled.bind(this));
|
||||||
|
this._typeButtonContainer.add_child(this._windowButton);
|
||||||
|
|
||||||
this._bottomRowContainer = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
this._bottomRowContainer = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
||||||
this._panel.add_child(this._bottomRowContainer);
|
this._panel.add_child(this._bottomRowContainer);
|
||||||
|
|
||||||
@ -766,6 +1001,7 @@ class ScreenshotUI extends St.Widget {
|
|||||||
this._bottomRowContainer.add_child(this._captureButton);
|
this._bottomRowContainer.add_child(this._captureButton);
|
||||||
|
|
||||||
this._monitorBins = [];
|
this._monitorBins = [];
|
||||||
|
this._windowSelectors = [];
|
||||||
this._rebuildMonitorBins();
|
this._rebuildMonitorBins();
|
||||||
|
|
||||||
Main.layoutManager.connect('monitors-changed', () => {
|
Main.layoutManager.connect('monitors-changed', () => {
|
||||||
@ -792,6 +1028,7 @@ class ScreenshotUI extends St.Widget {
|
|||||||
bin.destroy();
|
bin.destroy();
|
||||||
|
|
||||||
this._monitorBins = [];
|
this._monitorBins = [];
|
||||||
|
this._windowSelectors = [];
|
||||||
this._screenSelectors = [];
|
this._screenSelectors = [];
|
||||||
|
|
||||||
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
|
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
|
||||||
@ -802,6 +1039,18 @@ class ScreenshotUI extends St.Widget {
|
|||||||
this.insert_child_below(bin, this._primaryMonitorBin);
|
this.insert_child_below(bin, this._primaryMonitorBin);
|
||||||
this._monitorBins.push(bin);
|
this._monitorBins.push(bin);
|
||||||
|
|
||||||
|
const windowSelector = new UIWindowSelector(i, {
|
||||||
|
style_class: 'screenshot-ui-window-selector',
|
||||||
|
x_expand: true,
|
||||||
|
y_expand: true,
|
||||||
|
visible: this._windowButton.checked,
|
||||||
|
});
|
||||||
|
if (i === Main.layoutManager.primaryIndex)
|
||||||
|
windowSelector.add_style_pseudo_class('primary-monitor');
|
||||||
|
|
||||||
|
bin.add_child(windowSelector);
|
||||||
|
this._windowSelectors.push(windowSelector);
|
||||||
|
|
||||||
const screenSelector = new St.Button({
|
const screenSelector = new St.Button({
|
||||||
style_class: 'screenshot-ui-screen-selector',
|
style_class: 'screenshot-ui-screen-selector',
|
||||||
x_expand: true,
|
x_expand: true,
|
||||||
@ -845,6 +1094,32 @@ class ScreenshotUI extends St.Widget {
|
|||||||
if (!this.visible) {
|
if (!this.visible) {
|
||||||
// Screenshot UI is opening from completely closed state
|
// Screenshot UI is opening from completely closed state
|
||||||
// (rather than opening back from in process of closing).
|
// (rather than opening back from in process of closing).
|
||||||
|
for (const selector of this._windowSelectors)
|
||||||
|
selector.capture();
|
||||||
|
|
||||||
|
const windows =
|
||||||
|
this._windowSelectors.flatMap(selector => selector.windows());
|
||||||
|
for (const window of windows) {
|
||||||
|
window.connect('notify::checked', () => {
|
||||||
|
if (!window.checked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.toggle_mode = false;
|
||||||
|
|
||||||
|
for (const otherWindow of windows) {
|
||||||
|
if (window === otherWindow)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
otherWindow.toggle_mode = true;
|
||||||
|
otherWindow.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._windowButton.reactive = windows.length > 0;
|
||||||
|
if (!this._windowButton.reactive)
|
||||||
|
this._selectionButton.checked = true;
|
||||||
|
|
||||||
this._shooter = new Shell.Screenshot();
|
this._shooter = new Shell.Screenshot();
|
||||||
|
|
||||||
this._openingCoroutineInProgress = true;
|
this._openingCoroutineInProgress = true;
|
||||||
@ -903,6 +1178,8 @@ class ScreenshotUI extends St.Widget {
|
|||||||
this._stageScreenshot.set_content(null);
|
this._stageScreenshot.set_content(null);
|
||||||
|
|
||||||
this._areaSelector.reset();
|
this._areaSelector.reset();
|
||||||
|
for (const selector of this._windowSelectors)
|
||||||
|
selector.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
close(instantly = false) {
|
close(instantly = false) {
|
||||||
@ -925,6 +1202,7 @@ class ScreenshotUI extends St.Widget {
|
|||||||
_onSelectionButtonToggled() {
|
_onSelectionButtonToggled() {
|
||||||
if (this._selectionButton.checked) {
|
if (this._selectionButton.checked) {
|
||||||
this._selectionButton.toggle_mode = false;
|
this._selectionButton.toggle_mode = false;
|
||||||
|
this._windowButton.checked = false;
|
||||||
this._screenButton.checked = false;
|
this._screenButton.checked = false;
|
||||||
|
|
||||||
this._areaSelector.show();
|
this._areaSelector.show();
|
||||||
@ -956,6 +1234,7 @@ class ScreenshotUI extends St.Widget {
|
|||||||
if (this._screenButton.checked) {
|
if (this._screenButton.checked) {
|
||||||
this._screenButton.toggle_mode = false;
|
this._screenButton.toggle_mode = false;
|
||||||
this._selectionButton.checked = false;
|
this._selectionButton.checked = false;
|
||||||
|
this._windowButton.checked = false;
|
||||||
|
|
||||||
for (const selector of this._screenSelectors) {
|
for (const selector of this._screenSelectors) {
|
||||||
selector.show();
|
selector.show();
|
||||||
@ -981,6 +1260,36 @@ class ScreenshotUI extends St.Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onWindowButtonToggled() {
|
||||||
|
if (this._windowButton.checked) {
|
||||||
|
this._windowButton.toggle_mode = false;
|
||||||
|
this._selectionButton.checked = false;
|
||||||
|
this._screenButton.checked = false;
|
||||||
|
|
||||||
|
for (const selector of this._windowSelectors) {
|
||||||
|
selector.show();
|
||||||
|
selector.remove_all_transitions();
|
||||||
|
selector.ease({
|
||||||
|
opacity: 255,
|
||||||
|
duration: 200,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._windowButton.toggle_mode = true;
|
||||||
|
|
||||||
|
for (const selector of this._windowSelectors) {
|
||||||
|
selector.remove_all_transitions();
|
||||||
|
selector.ease({
|
||||||
|
opacity: 0,
|
||||||
|
duration: 200,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete: () => selector.hide(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_getSelectedGeometry() {
|
_getSelectedGeometry() {
|
||||||
let x, y, w, h;
|
let x, y, w, h;
|
||||||
|
|
||||||
@ -1029,6 +1338,38 @@ class ScreenshotUI extends St.Widget {
|
|||||||
).then(() => {
|
).then(() => {
|
||||||
stream.close(null);
|
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');
|
||||||
|
});
|
||||||
|
} else if (this._windowButton.checked) {
|
||||||
|
const window =
|
||||||
|
this._windowSelectors.flatMap(selector => selector.windows())
|
||||||
|
.find(win => win.checked);
|
||||||
|
if (!window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const content = window.windowContent;
|
||||||
|
if (!content) {
|
||||||
|
this.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const texture = content.get_texture();
|
||||||
|
const stream = Gio.MemoryOutputStream.new_resizable();
|
||||||
|
|
||||||
|
Shell.Screenshot.composite_to_stream(
|
||||||
|
texture,
|
||||||
|
0, 0, -1, -1,
|
||||||
|
stream
|
||||||
|
).then(() => {
|
||||||
|
stream.close(null);
|
||||||
|
|
||||||
const clipboard = St.Clipboard.get_default();
|
const clipboard = St.Clipboard.get_default();
|
||||||
clipboard.set_content(
|
clipboard.set_content(
|
||||||
St.ClipboardType.CLIPBOARD,
|
St.ClipboardType.CLIPBOARD,
|
||||||
|
Loading…
Reference in New Issue
Block a user