Codebase list gnome-shell-extensions / 4448920 extensions / window-list / windowPicker.js
4448920

Tree @4448920 (Download .tar.gz)

windowPicker.js @4448920raw · history · blame

/* exported WindowPicker, WindowPickerToggle */
const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;

const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const { WorkspacesDisplay } = imports.ui.workspacesView;

let MyWorkspacesDisplay = GObject.registerClass(
class MyWorkspacesDisplay extends WorkspacesDisplay {
    _init() {
        let workspaceManager = global.workspace_manager;

        this._workspaceAdjustment = new St.Adjustment({
            value: workspaceManager.get_active_workspace_index(),
            lower: 0,
            page_increment: 1,
            page_size: 1,
            step_increment: 0,
            upper: workspaceManager.n_workspaces,
        });

        this._nWorkspacesChangedId =
            workspaceManager.connect('notify::n-workspaces',
                this._updateAdjustment.bind(this));

        super._init(this._workspaceAdjustment);

        this._workspaceAdjustment.actor = this;

        this.add_constraint(
            new Layout.MonitorConstraint({
                primary: true,
                work_area: true,
            }));

        this._workareasChangedId = global.display.connect('workareas-changed',
            this._onWorkAreasChanged.bind(this));
        this._onWorkAreasChanged();
    }

    animateToOverview(...args) {
        if (!this._scrollEventId) {
            this._scrollEventId = Main.windowPicker.connect('scroll-event',
                this._onScrollEvent.bind(this));
        }

        super.animateToOverview(...args);
    }

    vfunc_hide(...args) {
        if (this._scrollEventId > 0)
            Main.windowPicker.disconnect(this._scrollEventId);
        this._scrollEventId = 0;

        super.vfunc_hide(...args);
    }

    _onWorkAreasChanged() {
        let { primaryIndex } = Main.layoutManager;
        this._actualGeometry =
            Main.layoutManager.getWorkAreaForMonitor(primaryIndex);
        this._syncWorkspacesActualGeometry();
    }

    _updateAdjustment() {
        let workspaceManager = global.workspace_manager;
        this._workspaceAdjustment.set({
            upper: workspaceManager.n_workspaces,
            value: workspaceManager.get_active_workspace_index(),
        });
    }

    _updateWorkspacesViews() {
        super._updateWorkspacesViews();

        this._workspacesViews.forEach(v => {
            Main.layoutManager.overviewGroup.remove_actor(v);
            Main.windowPicker.add_actor(v);
        });
    }

    _onDestroy() {
        if (this._workareasChangedId)
            global.display.disconnect(this._workareasChangedId);
        this._workareasChangedId = 0;

        if (this._nWorkspacesChangedId)
            global.workspace_manager.disconnect(this._nWorkspacesChangedId);
        this._nWorkspacesChangedId = 0;

        super._onDestroy();
    }
});

var WindowPicker = GObject.registerClass({
    Signals: {
        'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
    },
}, class extends Clutter.Actor {
    _init() {
        this._visible = false;
        this._modal = false;

        this._overlayKeyId = 0;
        this._stageKeyPressId = 0;

        super._init();

        this.connect('destroy', this._onDestroy.bind(this));

        global.bind_property('screen-width',
            this, 'width',
            GObject.BindingFlags.SYNC_CREATE);
        global.bind_property('screen-height',
            this, 'height',
            GObject.BindingFlags.SYNC_CREATE);

        this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true });
        this.add_child(this._backgroundGroup);

        this._backgroundGroup.connect('scroll-event', (a, ev) => {
            this.emit('scroll-event', ev);
        });

        // Trick WorkspacesDisplay constructor into adding actions here
        let addActionOrig = Main.overview.addAction;
        Main.overview.addAction = a => this._backgroundGroup.add_action(a);

        this._workspacesDisplay = new MyWorkspacesDisplay();
        this.add_child(this._workspacesDisplay);

        Main.overview.addAction = addActionOrig;

        this._bgManagers = [];

        this._monitorsChangedId = Main.layoutManager.connect('monitors-changed',
            this._updateBackgrounds.bind(this));
        this._updateBackgrounds();

        Main.uiGroup.insert_child_below(this, global.window_group);

        if (!Main.sessionMode.hasOverview) {
            this._overlayKeyId = global.display.connect('overlay-key', () => {
                if (!this._visible)
                    this.open();
                else
                    this.close();
            });
        }
    }

    get visible() {
        return this._visible;
    }

    open() {
        if (this._visible)
            return;

        this._visible = true;

        if (!this._syncGrab())
            return;

        this._fakeOverviewVisible(true);
        this._shadeBackgrounds();
        this._fakeOverviewAnimation();
        this._workspacesDisplay.animateToOverview(false);

        this._stageKeyPressId = global.stage.connect('key-press-event',
            (a, event) => {
                let sym = event.get_key_symbol();
                if (sym === Clutter.KEY_Escape) {
                    this.close();
                    return Clutter.EVENT_STOP;
                }
                return Clutter.EVENT_PROPAGATE;
            });

        this.emit('open-state-changed', this._visible);
    }

    close() {
        if (!this._visible)
            return;

        this._visible = false;

        if (!this._syncGrab())
            return;

        this._workspacesDisplay.animateFromOverview(false);
        this._unshadeBackgrounds();
        this._fakeOverviewAnimation(() => {
            this._workspacesDisplay.hide();
            this._fakeOverviewVisible(false);
        });

        global.stage.disconnect(this._stageKeyPressId);
        this._stageKeyPressId = 0;

        this.emit('open-state-changed', this._visible);
    }

    _fakeOverviewAnimation(onComplete) {
        Main.overview._animationInProgress = true;
        GLib.timeout_add(
            GLib.PRIORITY_DEFAULT,
            Overview.ANIMATION_TIME,
            () => {
                Main.overview._animationInProgress = false;
                if (onComplete)
                    onComplete();
            });
    }

    _fakeOverviewVisible(visible) {
        // Fake overview state for WorkspacesDisplay
        Main.overview._visible = visible;

        // Hide real windows
        Main.layoutManager._inOverview = visible;
        Main.layoutManager._updateVisibility();
    }

    _syncGrab() {
        if (this._visible) {
            if (this._modal)
                return true;

            this._modal = Main.pushModal(this, {
                actionMode: Shell.ActionMode.OVERVIEW,
            });

            if (!this._modal) {
                this.hide();
                return false;
            }
        } else if (this._modal) {
            Main.popModal(this);
            this._modal = false;
        }
        return true;
    }

    _onDestroy() {
        if (this._monitorsChangedId)
            Main.layoutManager.disconnect(this._monitorsChangedId);
        this._monitorsChangedId = 0;

        if (this._overlayKeyId)
            global.display.disconnect(this._overlayKeyId);
        this._overlayKeyId = 0;

        if (this._stageKeyPressId)
            global.stage.disconnect(this._stageKeyPressId);
        this._stageKeyPressId = 0;
    }

    _updateBackgrounds() {
        Main.overview._updateBackgrounds.call(this);
    }

    _shadeBackgrounds() {
        Main.overview._shadeBackgrounds.call(this);
    }

    _unshadeBackgrounds() {
        Main.overview._unshadeBackgrounds.call(this);
    }
});

var WindowPickerToggle = GObject.registerClass(
class WindowPickerToggle extends St.Button {
    _init() {
        let iconBin = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
        });
        iconBin.add_child(new St.Icon({
            icon_name: 'focus-windows-symbolic',
            icon_size: 16,
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        }));
        super._init({
            style_class: 'window-picker-toggle',
            child: iconBin,
            visible: !Main.sessionMode.hasOverview,
            toggle_mode: true,
        });

        this.connect('notify::checked', () => {
            if (this.checked)
                Main.windowPicker.open();
            else
                Main.windowPicker.close();
        });

        Main.windowPicker.connect('open-state-changed', () => {
            this.checked = Main.windowPicker.visible;
        });
    }
});