Blob Blame History Raw
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported init */

const { Shell } = imports.gi;

const ExtensionUtils = imports.misc.extensionUtils;

class WindowMover {
    constructor() {
        this._settings = ExtensionUtils.getSettings();
        this._appSystem = Shell.AppSystem.get_default();
        this._appConfigs = new Set();
        this._appData = new Map();

        this._appsChangedId = this._appSystem.connect(
            'installed-changed', this._updateAppData.bind(this));

        this._settings.connect('changed', this._updateAppConfigs.bind(this));
        this._updateAppConfigs();
    }

    _updateAppConfigs() {
        this._appConfigs.clear();

        this._settings.get_strv('application-list').forEach(appId => {
            this._appConfigs.add(appId);
        });

        this._updateAppData();
    }

    _updateAppData() {
        let ids = [...this._appConfigs.values()];
        let removedApps = [...this._appData.keys()].filter(
            a => !ids.includes(a.id)
        );
        removedApps.forEach(app => {
            app.disconnect(this._appData.get(app).windowsChangedId);
            this._appData.delete(app);
        });

        let addedApps = ids.map(id => this._appSystem.lookup_app(id)).filter(
            app => app != null && !this._appData.has(app)
        );
        addedApps.forEach(app => {
            let data = {
                windows: app.get_windows(),
                windowsChangedId: app.connect(
                    'windows-changed', this._appWindowsChanged.bind(this))
            };
            this._appData.set(app, data);
        });
    }

    destroy() {
        if (this._appsChangedId) {
            this._appSystem.disconnect(this._appsChangedId);
            this._appsChangedId = 0;
        }

        if (this._settings) {
            this._settings.run_dispose();
            this._settings = null;
        }

        this._appConfigs.clear();
        this._updateAppData();
    }

    _appWindowsChanged(app) {
        let data = this._appData.get(app);
        let windows = app.get_windows();

        // If get_compositor_private() returns non-NULL on a removed windows,
        // the window still exists and is just moved to a different workspace
        // or something; assume it'll be added back immediately, so keep it
        // to avoid moving it again
        windows.push(...data.windows.filter(
            w => !windows.includes(w) && w.get_compositor_private() != null
        ));

        windows.filter(w => !data.windows.includes(w)).forEach(window => {
            let leader = data.windows.find(w => w.get_pid() == window.get_pid());
            if (leader)
                window.change_workspace(leader.get_workspace());
        });
        data.windows = windows;
    }
}

class Extension {
    constructor() {
        this._winMover = null;
    }

    enable() {
        this._winMover = new WindowMover();
    }

    disable() {
        this._winMover.destroy();
        this._winMover = null;
    }
}

function init() {
    ExtensionUtils.initTranslations();
    return new Extension();
}