Blame js/ui/components/autorunManager.js

Packit d345d1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
Packit d345d1
Packit d345d1
const { Gio, St } = imports.gi;
Packit d345d1
Packit d345d1
const GnomeSession = imports.misc.gnomeSession;
Packit d345d1
const Main = imports.ui.main;
Packit d345d1
const MessageTray = imports.ui.messageTray;
Packit d345d1
Packit d345d1
const { loadInterfaceXML } = imports.misc.fileUtils;
Packit d345d1
Packit d345d1
// GSettings keys
Packit d345d1
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
Packit d345d1
const SETTING_DISABLE_AUTORUN = 'autorun-never';
Packit d345d1
const SETTING_START_APP = 'autorun-x-content-start-app';
Packit d345d1
const SETTING_IGNORE = 'autorun-x-content-ignore';
Packit d345d1
const SETTING_OPEN_FOLDER = 'autorun-x-content-open-folder';
Packit d345d1
Packit d345d1
var AutorunSetting = {
Packit d345d1
    RUN: 0,
Packit d345d1
    IGNORE: 1,
Packit d345d1
    FILES: 2,
Packit d345d1
    ASK: 3
Packit d345d1
};
Packit d345d1
Packit d345d1
// misc utils
Packit d345d1
function shouldAutorunMount(mount) {
Packit d345d1
    let root = mount.get_root();
Packit d345d1
    let volume = mount.get_volume();
Packit d345d1
Packit d345d1
    if (!volume || !volume.allowAutorun)
Packit d345d1
        return false;
Packit d345d1
Packit d345d1
    if (root.is_native() && isMountRootHidden(root))
Packit d345d1
        return false;
Packit d345d1
Packit d345d1
    return true;
Packit d345d1
}
Packit d345d1
Packit d345d1
function isMountRootHidden(root) {
Packit d345d1
    let path = root.get_path();
Packit d345d1
Packit d345d1
    // skip any mounts in hidden directory hierarchies
Packit d345d1
    return (path.indexOf('/.') != -1);
Packit d345d1
}
Packit d345d1
Packit d345d1
function isMountNonLocal(mount) {
Packit d345d1
    // If the mount doesn't have an associated volume, that means it's
Packit d345d1
    // an uninteresting filesystem. Most devices that we care about will
Packit d345d1
    // have a mount, like media players and USB sticks.
Packit d345d1
    let volume = mount.get_volume();
Packit d345d1
    if (volume == null)
Packit d345d1
        return true;
Packit d345d1
Packit d345d1
    return (volume.get_identifier("class") == "network");
Packit d345d1
}
Packit d345d1
Packit d345d1
function startAppForMount(app, mount) {
Packit d345d1
    let files = [];
Packit d345d1
    let root = mount.get_root();
Packit d345d1
    let retval = false;
Packit d345d1
Packit d345d1
    files.push(root);
Packit d345d1
Packit d345d1
    try {
Packit d345d1
        retval = app.launch(files, 
Packit d345d1
                            global.create_app_launch_context(0, -1));
Packit d345d1
    } catch (e) {
Packit d345d1
        log('Unable to launch the application ' + app.get_name()
Packit d345d1
            + ': ' + e.toString());
Packit d345d1
    }
Packit d345d1
Packit d345d1
    return retval;
Packit d345d1
}
Packit d345d1
Packit d345d1
/******************************************/
Packit d345d1
Packit d345d1
const HotplugSnifferIface = loadInterfaceXML('org.gnome.Shell.HotplugSniffer');
Packit d345d1
const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface);
Packit d345d1
function HotplugSniffer() {
Packit d345d1
    return new HotplugSnifferProxy(Gio.DBus.session,
Packit d345d1
                                   'org.gnome.Shell.HotplugSniffer',
Packit d345d1
                                   '/org/gnome/Shell/HotplugSniffer');
Packit d345d1
}
Packit d345d1
Packit d345d1
var ContentTypeDiscoverer = class {
Packit d345d1
    constructor(callback) {
Packit d345d1
        this._callback = callback;
Packit d345d1
        this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    guessContentTypes(mount) {
Packit d345d1
        let autorunEnabled = !this._settings.get_boolean(SETTING_DISABLE_AUTORUN);
Packit d345d1
        let shouldScan = autorunEnabled && !isMountNonLocal(mount);
Packit d345d1
Packit d345d1
        if (shouldScan) {
Packit d345d1
            // guess mount's content types using GIO
Packit d345d1
            mount.guess_content_type(false, null,
Packit d345d1
                                     this._onContentTypeGuessed.bind(this));
Packit d345d1
        } else {
Packit d345d1
            this._emitCallback(mount, []);
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onContentTypeGuessed(mount, res) {
Packit d345d1
        let contentTypes = [];
Packit d345d1
Packit d345d1
        try {
Packit d345d1
            contentTypes = mount.guess_content_type_finish(res);
Packit d345d1
        } catch (e) {
Packit d345d1
            log('Unable to guess content types on added mount ' + mount.get_name()
Packit d345d1
                + ': ' + e.toString());
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (contentTypes.length) {
Packit d345d1
            this._emitCallback(mount, contentTypes);
Packit d345d1
        } else {
Packit d345d1
            let root = mount.get_root();
Packit d345d1
Packit d345d1
            let hotplugSniffer = new HotplugSniffer();
Packit d345d1
            hotplugSniffer.SniffURIRemote(root.get_uri(),
Packit d345d1
                 ([contentTypes]) => {
Packit d345d1
                     this._emitCallback(mount, contentTypes);
Packit d345d1
                 });
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _emitCallback(mount, contentTypes) {
Packit d345d1
        if (!contentTypes)
Packit d345d1
            contentTypes = [];
Packit d345d1
Packit d345d1
        // we're not interested in win32 software content types here
Packit d345d1
        contentTypes = contentTypes.filter(
Packit d345d1
            type => (type != 'x-content/win32-software')
Packit d345d1
        );
Packit d345d1
Packit d345d1
        let apps = [];
Packit d345d1
        contentTypes.forEach(type => {
Packit d345d1
            let app = Gio.app_info_get_default_for_type(type, false);
Packit d345d1
Packit d345d1
            if (app)
Packit d345d1
                apps.push(app);
Packit d345d1
        });
Packit d345d1
Packit d345d1
        if (apps.length == 0)
Packit d345d1
            apps.push(Gio.app_info_get_default_for_type('inode/directory', false));
Packit d345d1
Packit d345d1
        this._callback(mount, apps, contentTypes);
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var AutorunManager = class {
Packit d345d1
    constructor() {
Packit d345d1
        this._session = new GnomeSession.SessionManager();
Packit d345d1
        this._volumeMonitor = Gio.VolumeMonitor.get();
Packit d345d1
Packit d345d1
        this._dispatcher = new AutorunDispatcher(this);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    enable() {
Packit d345d1
        this._mountAddedId = this._volumeMonitor.connect('mount-added', this._onMountAdded.bind(this));
Packit d345d1
        this._mountRemovedId = this._volumeMonitor.connect('mount-removed', this._onMountRemoved.bind(this));
Packit d345d1
    }
Packit d345d1
Packit d345d1
    disable() {
Packit d345d1
        this._volumeMonitor.disconnect(this._mountAddedId);
Packit d345d1
        this._volumeMonitor.disconnect(this._mountRemovedId);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onMountAdded(monitor, mount) {
Packit d345d1
        // don't do anything if our session is not the currently
Packit d345d1
        // active one
Packit d345d1
        if (!this._session.SessionIsActive)
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        let discoverer = new ContentTypeDiscoverer((mount, apps, contentTypes) => {
Packit d345d1
            this._dispatcher.addMount(mount, apps, contentTypes);
Packit d345d1
        });
Packit d345d1
        discoverer.guessContentTypes(mount);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onMountRemoved(monitor, mount) {
Packit d345d1
        this._dispatcher.removeMount(mount);
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var AutorunDispatcher = class {
Packit d345d1
    constructor(manager) {
Packit d345d1
        this._manager = manager;
Packit d345d1
        this._sources = [];
Packit d345d1
        this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _getAutorunSettingForType(contentType) {
Packit d345d1
        let runApp = this._settings.get_strv(SETTING_START_APP);
Packit d345d1
        if (runApp.indexOf(contentType) != -1)
Packit d345d1
            return AutorunSetting.RUN;
Packit d345d1
Packit d345d1
        let ignore = this._settings.get_strv(SETTING_IGNORE);
Packit d345d1
        if (ignore.indexOf(contentType) != -1)
Packit d345d1
            return AutorunSetting.IGNORE;
Packit d345d1
Packit d345d1
        let openFiles = this._settings.get_strv(SETTING_OPEN_FOLDER);
Packit d345d1
        if (openFiles.indexOf(contentType) != -1)
Packit d345d1
            return AutorunSetting.FILES;
Packit d345d1
Packit d345d1
        return AutorunSetting.ASK;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _getSourceForMount(mount) {
Packit d345d1
        let filtered = this._sources.filter(source => (source.mount == mount));
Packit d345d1
Packit d345d1
        // we always make sure not to add two sources for the same
Packit d345d1
        // mount in addMount(), so it's safe to assume filtered.length
Packit d345d1
        // is always either 1 or 0.
Packit d345d1
        if (filtered.length == 1)
Packit d345d1
            return filtered[0];
Packit d345d1
Packit d345d1
        return null;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _addSource(mount, apps) {
Packit d345d1
        // if we already have a source showing for this 
Packit d345d1
        // mount, return
Packit d345d1
        if (this._getSourceForMount(mount))
Packit d345d1
            return;
Packit d345d1
     
Packit d345d1
        // add a new source
Packit d345d1
        this._sources.push(new AutorunSource(this._manager, mount, apps));
Packit d345d1
    }
Packit d345d1
Packit d345d1
    addMount(mount, apps, contentTypes) {
Packit d345d1
        // if autorun is disabled globally, return
Packit d345d1
        if (this._settings.get_boolean(SETTING_DISABLE_AUTORUN))
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        // if the mount doesn't want to be autorun, return
Packit d345d1
        if (!shouldAutorunMount(mount))
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        let setting;
Packit d345d1
        if (contentTypes.length > 0)
Packit d345d1
            setting = this._getAutorunSettingForType(contentTypes[0]);
Packit d345d1
        else
Packit d345d1
            setting = AutorunSetting.ASK;
Packit d345d1
Packit d345d1
        // check at the settings for the first content type
Packit d345d1
        // to see whether we should ask
Packit d345d1
        if (setting == AutorunSetting.IGNORE)
Packit d345d1
            return; // return right away
Packit d345d1
Packit d345d1
        let success = false;
Packit d345d1
        let app = null;
Packit d345d1
Packit d345d1
        if (setting == AutorunSetting.RUN) {
Packit d345d1
            app = Gio.app_info_get_default_for_type(contentTypes[0], false);
Packit d345d1
        } else if (setting == AutorunSetting.FILES) {
Packit d345d1
            app = Gio.app_info_get_default_for_type('inode/directory', false);
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (app)
Packit d345d1
            success = startAppForMount(app, mount);
Packit d345d1
Packit d345d1
        // we fallback here also in case the settings did not specify 'ask',
Packit d345d1
        // but we failed launching the default app or the default file manager
Packit d345d1
        if (!success)
Packit d345d1
            this._addSource(mount, apps);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    removeMount(mount) {
Packit d345d1
        let source = this._getSourceForMount(mount);
Packit d345d1
        
Packit d345d1
        // if we aren't tracking this mount, don't do anything
Packit d345d1
        if (!source)
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        // destroy the notification source
Packit d345d1
        source.destroy();
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var AutorunSource = class extends MessageTray.Source {
Packit d345d1
    constructor(manager, mount, apps) {
Packit d345d1
        super(mount.get_name());
Packit d345d1
Packit d345d1
        this._manager = manager;
Packit d345d1
        this.mount = mount;
Packit d345d1
        this.apps = apps;
Packit d345d1
Packit d345d1
        this._notification = new AutorunNotification(this._manager, this);
Packit d345d1
Packit d345d1
        // add ourselves as a source, and popup the notification
Packit d345d1
        Main.messageTray.add(this);
Packit d345d1
        this.notify(this._notification);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    getIcon() {
Packit d345d1
        return this.mount.get_icon();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _createPolicy() {
Packit d345d1
        return new MessageTray.NotificationApplicationPolicy('org.gnome.Nautilus');
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var AutorunNotification = class extends MessageTray.Notification {
Packit d345d1
    constructor(manager, source) {
Packit d345d1
        super(source, source.title);
Packit d345d1
Packit d345d1
        this._manager = manager;
Packit d345d1
        this._mount = source.mount;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    createBanner() {
Packit d345d1
        let banner = new MessageTray.NotificationBanner(this);
Packit d345d1
Packit d345d1
        this.source.apps.forEach(app => {
Packit d345d1
            let actor = this._buttonForApp(app);
Packit d345d1
Packit d345d1
            if (actor)
Packit d345d1
                banner.addButton(actor);
Packit d345d1
        });
Packit d345d1
Packit d345d1
        return banner;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _buttonForApp(app) {
Packit d345d1
        let box = new St.BoxLayout();
Packit d345d1
        let icon = new St.Icon({ gicon: app.get_icon(),
Packit d345d1
                                 style_class: 'hotplug-notification-item-icon' });
Packit d345d1
        box.add(icon);
Packit d345d1
Packit d345d1
        let label = new St.Bin({ y_align: St.Align.MIDDLE,
Packit d345d1
                                 child: new St.Label
Packit d345d1
                                 ({ text: _("Open with %s").format(app.get_name()) })
Packit d345d1
                               });
Packit d345d1
        box.add(label);
Packit d345d1
Packit d345d1
        let button = new St.Button({ child: box,
Packit d345d1
                                     x_fill: true,
Packit d345d1
                                     x_align: St.Align.START,
Packit d345d1
                                     x_expand: true,
Packit d345d1
                                     button_mask: St.ButtonMask.ONE,
Packit d345d1
                                     style_class: 'hotplug-notification-item button' });
Packit d345d1
Packit d345d1
        button.connect('clicked', () => {
Packit d345d1
            startAppForMount(app, this._mount);
Packit d345d1
            this.destroy();
Packit d345d1
        });
Packit d345d1
Packit d345d1
        return button;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    activate() {
Packit d345d1
        super.activate();
Packit d345d1
Packit d345d1
        let app = Gio.app_info_get_default_for_type('inode/directory', false);
Packit d345d1
        startAppForMount(app, this._mount);
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var Component = AutorunManager;