Blame js/ui/screenshot.js

Packit d345d1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit d345d1
Packit d345d1
const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;
Packit d345d1
const Signals = imports.signals;
Packit d345d1
Packit d345d1
const GrabHelper = imports.ui.grabHelper;
Packit d345d1
const Lightbox = imports.ui.lightbox;
Packit d345d1
const Main = imports.ui.main;
Packit d345d1
const Tweener = imports.ui.tweener;
Packit d345d1
Packit d345d1
const { loadInterfaceXML } = imports.misc.fileUtils;
Packit d345d1
Packit d345d1
const ScreenshotIface = loadInterfaceXML('org.gnome.Shell.Screenshot');
Packit d345d1
Packit d345d1
var ScreenshotService = class {
Packit d345d1
    constructor() {
Packit d345d1
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this);
Packit d345d1
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot');
Packit d345d1
Packit d345d1
        this._screenShooter = new Map();
Packit d345d1
Packit d345d1
        this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });
Packit d345d1
Packit d345d1
        Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _createScreenshot(invocation, needsDisk=true) {
Packit d345d1
        let lockedDown = false;
Packit d345d1
        if (needsDisk)
Packit d345d1
            lockedDown = this._lockdownSettings.get_boolean('disable-save-to-disk')
Packit d345d1
Packit d345d1
        let sender = invocation.get_sender();
Packit d345d1
        if (this._screenShooter.has(sender) || lockedDown) {
Packit d345d1
            invocation.return_value(GLib.Variant.new('(bs)', [false, '']));
Packit d345d1
            return null;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        let shooter = new Shell.Screenshot();
Packit d345d1
        shooter._watchNameId =
Packit d345d1
                        Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
Packit d345d1
                                           this._onNameVanished.bind(this));
Packit d345d1
Packit d345d1
        this._screenShooter.set(sender, shooter);
Packit d345d1
Packit d345d1
        return shooter;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onNameVanished(connection, name) {
Packit d345d1
        this._removeShooterForSender(name);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _removeShooterForSender(sender) {
Packit d345d1
        let shooter = this._screenShooter.get(sender);
Packit d345d1
        if (!shooter)
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        Gio.bus_unwatch_name(shooter._watchNameId);
Packit d345d1
        this._screenShooter.delete(sender);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _checkArea(x, y, width, height) {
Packit d345d1
        return x >= 0 && y >= 0 &&
Packit d345d1
               width > 0 && height > 0 &&
Packit d345d1
               x + width <= global.screen_width &&
Packit d345d1
               y + height <= global.screen_height;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onScreenshotComplete(result, area, filenameUsed, flash, invocation) {
Packit d345d1
        if (result) {
Packit d345d1
            if (flash) {
Packit d345d1
                let flashspot = new Flashspot(area);
Packit d345d1
                flashspot.fire(() => {
Packit d345d1
                    this._removeShooterForSender(invocation.get_sender());
Packit d345d1
                });
Packit d345d1
            }
Packit d345d1
            else
Packit d345d1
                this._removeShooterForSender(invocation.get_sender());
Packit d345d1
        }
Packit d345d1
Packit d345d1
        let retval = GLib.Variant.new('(bs)', [result, filenameUsed]);
Packit d345d1
        invocation.return_value(retval);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _scaleArea(x, y, width, height) {
Packit d345d1
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
Packit d345d1
        x *= scaleFactor;
Packit d345d1
        y *= scaleFactor;
Packit d345d1
        width *= scaleFactor;
Packit d345d1
        height *= scaleFactor;
Packit d345d1
        return [x, y, width, height];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _unscaleArea(x, y, width, height) {
Packit d345d1
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
Packit d345d1
        x /= scaleFactor;
Packit d345d1
        y /= scaleFactor;
Packit d345d1
        width /= scaleFactor;
Packit d345d1
        height /= scaleFactor;
Packit d345d1
        return [x, y, width, height];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    ScreenshotAreaAsync(params, invocation) {
Packit d345d1
        let [x, y, width, height, flash, filename] = params;
Packit d345d1
        [x, y, width, height] = this._scaleArea(x, y, width, height);
Packit d345d1
        if (!this._checkArea(x, y, width, height)) {
Packit d345d1
            invocation.return_error_literal(Gio.IOErrorEnum,
Packit d345d1
                                            Gio.IOErrorEnum.CANCELLED,
Packit d345d1
                                            "Invalid params");
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
        let screenshot = this._createScreenshot(invocation);
Packit d345d1
        if (!screenshot)
Packit d345d1
            return;
Packit d345d1
        screenshot.screenshot_area (x, y, width, height, filename,
Packit d345d1
            (o, res) => {
Packit d345d1
                try {
Packit d345d1
                    let [result, area, filenameUsed] =
Packit d345d1
                        screenshot.screenshot_area_finish(res);
Packit d345d1
                    this._onScreenshotComplete(result, area, filenameUsed,
Packit d345d1
                                               flash, invocation);
Packit d345d1
                } catch (e) {
Packit d345d1
                    invocation.return_gerror (e);
Packit d345d1
                }
Packit d345d1
            });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    ScreenshotWindowAsync(params, invocation) {
Packit d345d1
        let [include_frame, include_cursor, flash, filename] = params;
Packit d345d1
        let screenshot = this._createScreenshot(invocation);
Packit d345d1
        if (!screenshot)
Packit d345d1
            return;
Packit d345d1
        screenshot.screenshot_window (include_frame, include_cursor, filename,
Packit d345d1
            (o, res) => {
Packit d345d1
                try {
Packit d345d1
                    let [result, area, filenameUsed] =
Packit d345d1
                        screenshot.screenshot_window_finish(res);
Packit d345d1
                    this._onScreenshotComplete(result, area, filenameUsed,
Packit d345d1
                                               flash, invocation);
Packit d345d1
                } catch (e) {
Packit d345d1
                    invocation.return_gerror (e);
Packit d345d1
                }
Packit d345d1
            });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    ScreenshotAsync(params, invocation) {
Packit d345d1
        let [include_cursor, flash, filename] = params;
Packit d345d1
        let screenshot = this._createScreenshot(invocation);
Packit d345d1
        if (!screenshot)
Packit d345d1
            return;
Packit d345d1
        screenshot.screenshot(include_cursor, filename,
Packit d345d1
            (o, res) => {
Packit d345d1
                try {
Packit d345d1
                    let [result, area, filenameUsed] =
Packit d345d1
                        screenshot.screenshot_finish(res);
Packit d345d1
                    this._onScreenshotComplete(result, area, filenameUsed,
Packit d345d1
                                               flash, invocation);
Packit d345d1
                } catch (e) {
Packit d345d1
                    invocation.return_gerror (e);
Packit d345d1
                }
Packit d345d1
            });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    SelectAreaAsync(params, invocation) {
Packit d345d1
        let selectArea = new SelectArea();
Packit d345d1
        selectArea.show();
Packit d345d1
        selectArea.connect('finished', (selectArea, areaRectangle) => {
Packit d345d1
            if (areaRectangle) {
Packit d345d1
                let retRectangle = this._unscaleArea(areaRectangle.x, areaRectangle.y,
Packit d345d1
                    areaRectangle.width, areaRectangle.height);
Packit d345d1
                let retval = GLib.Variant.new('(iiii)', retRectangle);
Packit d345d1
                invocation.return_value(retval);
Packit d345d1
            } else {
Packit d345d1
                invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
Packit d345d1
                    "Operation was cancelled");
Packit d345d1
            }
Packit d345d1
        });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    FlashAreaAsync(params, invocation) {
Packit d345d1
        let [x, y, width, height] = params;
Packit d345d1
        [x, y, width, height] = this._scaleArea(x, y, width, height);
Packit d345d1
        if (!this._checkArea(x, y, width, height)) {
Packit d345d1
            invocation.return_error_literal(Gio.IOErrorEnum,
Packit d345d1
                                            Gio.IOErrorEnum.CANCELLED,
Packit d345d1
                                            "Invalid params");
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
        let flashspot = new Flashspot({ x : x, y : y, width: width, height: height});
Packit d345d1
        flashspot.fire();
Packit d345d1
        invocation.return_value(null);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    PickColorAsync(params, invocation) {
Packit d345d1
        let pickPixel = new PickPixel();
Packit d345d1
        pickPixel.show();
Packit d345d1
        pickPixel.connect('finished', (pickPixel, coords) => {
Packit d345d1
            if (coords) {
Packit d345d1
                let screenshot = this._createScreenshot(invocation, false);
Packit d345d1
                if (!screenshot)
Packit d345d1
                    return;
Packit d345d1
                screenshot.pick_color(...coords, (o, res) => {
Packit d345d1
                    let [success, color] = screenshot.pick_color_finish(res);
Packit d345d1
                    let { red, green, blue } = color;
Packit d345d1
                    let retval = GLib.Variant.new('(a{sv})', [{
Packit d345d1
                        color: GLib.Variant.new('(ddd)', [
Packit d345d1
                            red / 255.0,
Packit d345d1
                            green / 255.0,
Packit d345d1
                            blue / 255.0
Packit d345d1
                        ])
Packit d345d1
                    }]);
Packit d345d1
                    this._removeShooterForSender(invocation.get_sender());
Packit d345d1
                    invocation.return_value(retval);
Packit d345d1
                });
Packit d345d1
            } else {
Packit d345d1
                invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
Packit d345d1
                    "Operation was cancelled");
Packit d345d1
            }
Packit d345d1
        });
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var SelectArea = class {
Packit d345d1
    constructor() {
Packit d345d1
        this._startX = -1;
Packit d345d1
        this._startY = -1;
Packit d345d1
        this._lastX = 0;
Packit d345d1
        this._lastY = 0;
Packit d345d1
        this._result = null;
Packit d345d1
Packit d345d1
        this._group = new St.Widget({ visible: false,
Packit d345d1
                                      reactive: true,
Packit d345d1
                                      x: 0,
Packit d345d1
                                      y: 0 });
Packit d345d1
        Main.uiGroup.add_actor(this._group);
Packit d345d1
Packit d345d1
        this._grabHelper = new GrabHelper.GrabHelper(this._group);
Packit d345d1
Packit d345d1
        this._group.connect('button-press-event',
Packit d345d1
                            this._onButtonPress.bind(this));
Packit d345d1
        this._group.connect('button-release-event',
Packit d345d1
                            this._onButtonRelease.bind(this));
Packit d345d1
        this._group.connect('motion-event',
Packit d345d1
                            this._onMotionEvent.bind(this));
Packit d345d1
Packit d345d1
        let constraint = new Clutter.BindConstraint({ source: global.stage,
Packit d345d1
                                                      coordinate: Clutter.BindCoordinate.ALL });
Packit d345d1
        this._group.add_constraint(constraint);
Packit d345d1
Packit d345d1
        this._rubberband = new St.Widget({
Packit d345d1
            style_class: 'select-area-rubberband',
Packit d345d1
            visible: false
Packit d345d1
        });
Packit d345d1
        this._group.add_actor(this._rubberband);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    show() {
Packit d345d1
        if (!this._grabHelper.grab({ actor: this._group,
Packit d345d1
                                     onUngrab: this._onUngrab.bind(this) }))
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        global.display.set_cursor(Meta.Cursor.CROSSHAIR);
Packit d345d1
        Main.uiGroup.set_child_above_sibling(this._group, null);
Packit d345d1
        this._group.visible = true;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _getGeometry() {
Packit d345d1
        return { x: Math.min(this._startX, this._lastX),
Packit d345d1
                 y: Math.min(this._startY, this._lastY),
Packit d345d1
                 width: Math.abs(this._startX - this._lastX) + 1,
Packit d345d1
                 height: Math.abs(this._startY - this._lastY) + 1 };
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onMotionEvent(actor, event) {
Packit d345d1
        if (this._startX == -1 || this._startY == -1)
Packit d345d1
            return Clutter.EVENT_PROPAGATE;
Packit d345d1
Packit d345d1
        [this._lastX, this._lastY] = event.get_coords();
Packit d345d1
        this._lastX = Math.floor(this._lastX);
Packit d345d1
        this._lastY = Math.floor(this._lastY);
Packit d345d1
        let geometry = this._getGeometry();
Packit d345d1
Packit d345d1
        this._rubberband.set_position(geometry.x, geometry.y);
Packit d345d1
        this._rubberband.set_size(geometry.width, geometry.height);
Packit d345d1
        this._rubberband.show();
Packit d345d1
Packit d345d1
        return Clutter.EVENT_PROPAGATE;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onButtonPress(actor, event) {
Packit d345d1
        [this._startX, this._startY] = event.get_coords();
Packit d345d1
        this._startX = Math.floor(this._startX);
Packit d345d1
        this._startY = Math.floor(this._startY);
Packit d345d1
        this._rubberband.set_position(this._startX, this._startY);
Packit d345d1
Packit d345d1
        return Clutter.EVENT_PROPAGATE;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onButtonRelease(actor, event) {
Packit d345d1
        this._result = this._getGeometry();
Packit d345d1
        Tweener.addTween(this._group,
Packit d345d1
                         { opacity: 0,
Packit d345d1
                           time: 0.2,
Packit d345d1
                           transition: 'easeOutQuad',
Packit d345d1
                           onComplete: () => {
Packit d345d1
                               this._grabHelper.ungrab();
Packit d345d1
                           }
Packit d345d1
                         });
Packit d345d1
        return Clutter.EVENT_PROPAGATE;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onUngrab() {
Packit d345d1
        global.display.set_cursor(Meta.Cursor.DEFAULT);
Packit d345d1
        this.emit('finished', this._result);
Packit d345d1
Packit d345d1
        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Packit d345d1
            this._group.destroy();
Packit d345d1
            return GLib.SOURCE_REMOVE;
Packit d345d1
        });
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Signals.addSignalMethods(SelectArea.prototype);
Packit d345d1
Packit d345d1
var PickPixel = class {
Packit d345d1
    constructor() {
Packit d345d1
        this._result = null;
Packit d345d1
Packit d345d1
        this._group = new St.Widget({ visible: false,
Packit d345d1
                                      reactive: true });
Packit d345d1
        Main.uiGroup.add_actor(this._group);
Packit d345d1
Packit d345d1
        this._grabHelper = new GrabHelper.GrabHelper(this._group);
Packit d345d1
Packit d345d1
        this._group.connect('button-release-event',
Packit d345d1
                            this._onButtonRelease.bind(this));
Packit d345d1
Packit d345d1
        let constraint = new Clutter.BindConstraint({ source: global.stage,
Packit d345d1
                                                      coordinate: Clutter.BindCoordinate.ALL });
Packit d345d1
        this._group.add_constraint(constraint);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    show() {
Packit d345d1
        if (!this._grabHelper.grab({ actor: this._group,
Packit d345d1
                                     onUngrab: this._onUngrab.bind(this) }))
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        global.display.set_cursor(Meta.Cursor.CROSSHAIR);
Packit d345d1
        Main.uiGroup.set_child_above_sibling(this._group, null);
Packit d345d1
        this._group.visible = true;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onButtonRelease(actor, event) {
Packit d345d1
        this._result = event.get_coords();
Packit d345d1
        this._grabHelper.ungrab();
Packit d345d1
        return Clutter.EVENT_PROPAGATE;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onUngrab() {
Packit d345d1
        global.display.set_cursor(Meta.Cursor.DEFAULT);
Packit d345d1
        this.emit('finished', this._result);
Packit d345d1
Packit d345d1
        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
Packit d345d1
            this._group.destroy();
Packit d345d1
            return GLib.SOURCE_REMOVE;
Packit d345d1
        });
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Signals.addSignalMethods(PickPixel.prototype);
Packit d345d1
Packit d345d1
var FLASHSPOT_ANIMATION_OUT_TIME = 0.5; // seconds
Packit d345d1
Packit d345d1
var Flashspot = class extends Lightbox.Lightbox {
Packit d345d1
    constructor(area) {
Packit d345d1
        super(Main.uiGroup, { inhibitEvents: true,
Packit d345d1
                              width: area.width,
Packit d345d1
                              height: area.height });
Packit d345d1
Packit d345d1
        this.actor.style_class = 'flashspot';
Packit d345d1
        this.actor.set_position(area.x, area.y);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    fire(doneCallback) {
Packit d345d1
        this.actor.show();
Packit d345d1
        this.actor.opacity = 255;
Packit d345d1
        Tweener.addTween(this.actor,
Packit d345d1
                         { opacity: 0,
Packit d345d1
                           time: FLASHSPOT_ANIMATION_OUT_TIME,
Packit d345d1
                           transition: 'easeOutQuad',
Packit d345d1
                           onComplete: () => {
Packit d345d1
                               if (doneCallback)
Packit d345d1
                                   doneCallback();
Packit d345d1
                               this.destroy();
Packit d345d1
                           }
Packit d345d1
                         });
Packit d345d1
    }
Packit d345d1
};