211 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// SPDX-FileCopyrightText: 2011 Giovanni Campagna <gcampagna@src.gnome.org>
 | 
						|
// SPDX-FileCopyrightText: 2018 Florian Müllner <fmuellner@gnome.org>
 | 
						|
//
 | 
						|
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
						|
 | 
						|
// Drive menu extension
 | 
						|
import Clutter from 'gi://Clutter';
 | 
						|
import Gio from 'gi://Gio';
 | 
						|
import GObject from 'gi://GObject';
 | 
						|
import Shell from 'gi://Shell';
 | 
						|
import St from 'gi://St';
 | 
						|
 | 
						|
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
 | 
						|
 | 
						|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
 | 
						|
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
 | 
						|
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
 | 
						|
import * as ShellMountOperation from 'resource:///org/gnome/shell/ui/shellMountOperation.js';
 | 
						|
 | 
						|
Gio._promisify(Gio.File.prototype, 'query_filesystem_info_async');
 | 
						|
 | 
						|
class MountMenuItem extends PopupMenu.PopupBaseMenuItem {
 | 
						|
    static {
 | 
						|
        GObject.registerClass(this);
 | 
						|
    }
 | 
						|
 | 
						|
    constructor(mount) {
 | 
						|
        super({
 | 
						|
            style_class: 'drive-menu-item',
 | 
						|
        });
 | 
						|
 | 
						|
        this.label = new St.Label({
 | 
						|
            text: mount.get_name(),
 | 
						|
            x_expand: true,
 | 
						|
            y_align: Clutter.ActorAlign.CENTER,
 | 
						|
        });
 | 
						|
        this.add_child(this.label);
 | 
						|
        this.label_actor = this.label;
 | 
						|
 | 
						|
        this.mount = mount;
 | 
						|
 | 
						|
        let ejectIcon = new St.Icon({
 | 
						|
            icon_name: 'media-eject-symbolic',
 | 
						|
            style_class: 'popup-menu-icon',
 | 
						|
        });
 | 
						|
        let ejectButton = new St.Button({
 | 
						|
            child: ejectIcon,
 | 
						|
            style_class: 'button',
 | 
						|
        });
 | 
						|
        ejectButton.connect('clicked', this._eject.bind(this));
 | 
						|
        this.add_child(ejectButton);
 | 
						|
 | 
						|
        this.hide();
 | 
						|
 | 
						|
        mount.connectObject('changed',
 | 
						|
            () => this._syncVisibility(), this);
 | 
						|
        this._syncVisibility();
 | 
						|
    }
 | 
						|
 | 
						|
    async _isInteresting() {
 | 
						|
        if (!this.mount.can_eject() && !this.mount.can_unmount())
 | 
						|
            return false;
 | 
						|
        if (this.mount.is_shadowed())
 | 
						|
            return false;
 | 
						|
 | 
						|
        let volume = this.mount.get_volume();
 | 
						|
 | 
						|
        if (volume)
 | 
						|
            return volume.get_identifier('class') !== 'network';
 | 
						|
 | 
						|
        const root = this.mount.get_root();
 | 
						|
 | 
						|
        try {
 | 
						|
            const attr = Gio.FILE_ATTRIBUTE_FILESYSTEM_REMOTE;
 | 
						|
            const info = await root.query_filesystem_info_async(attr, null);
 | 
						|
            return !info.get_attribute_boolean(attr);
 | 
						|
        } catch (e) {
 | 
						|
            log(`Failed to query filesystem: ${e.message}`);
 | 
						|
        }
 | 
						|
 | 
						|
        // Hack, fall back to looking at GType
 | 
						|
        return Gio._LocalFilePrototype.isPrototypeOf(root);
 | 
						|
    }
 | 
						|
 | 
						|
    async _syncVisibility() {
 | 
						|
        this.visible = await this._isInteresting();
 | 
						|
    }
 | 
						|
 | 
						|
    _eject() {
 | 
						|
        let unmountArgs = [
 | 
						|
            Gio.MountUnmountFlags.NONE,
 | 
						|
            new ShellMountOperation.ShellMountOperation(this.mount).mountOp,
 | 
						|
            null, // Gio.Cancellable
 | 
						|
        ];
 | 
						|
 | 
						|
        if (this.mount.can_eject()) {
 | 
						|
            this.mount.eject_with_operation(...unmountArgs,
 | 
						|
                this._ejectFinish.bind(this));
 | 
						|
        } else {
 | 
						|
            this.mount.unmount_with_operation(...unmountArgs,
 | 
						|
                this._unmountFinish.bind(this));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    _unmountFinish(mount, result) {
 | 
						|
        try {
 | 
						|
            mount.unmount_with_operation_finish(result);
 | 
						|
        } catch (e) {
 | 
						|
            this._reportFailure(e);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    _ejectFinish(mount, result) {
 | 
						|
        try {
 | 
						|
            mount.eject_with_operation_finish(result);
 | 
						|
        } catch (e) {
 | 
						|
            this._reportFailure(e);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    _reportFailure(exception) {
 | 
						|
        // TRANSLATORS: %s is the filesystem name
 | 
						|
        let msg = _('Ejecting drive “%s” failed:').format(this.mount.get_name());
 | 
						|
        Main.notifyError(msg, exception.message);
 | 
						|
    }
 | 
						|
 | 
						|
    activate(event) {
 | 
						|
        let uri = this.mount.get_root().get_uri();
 | 
						|
        let context = global.create_app_launch_context(event.get_time(), -1);
 | 
						|
        Gio.AppInfo.launch_default_for_uri(uri, context);
 | 
						|
 | 
						|
        super.activate(event);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
class DriveMenu extends PanelMenu.Button {
 | 
						|
    static {
 | 
						|
        GObject.registerClass(this);
 | 
						|
    }
 | 
						|
 | 
						|
    constructor() {
 | 
						|
        super(0.5, _('Removable devices'));
 | 
						|
 | 
						|
        let icon = new St.Icon({
 | 
						|
            icon_name: 'media-eject-symbolic',
 | 
						|
            style_class: 'system-status-icon',
 | 
						|
        });
 | 
						|
 | 
						|
        this.add_child(icon);
 | 
						|
 | 
						|
        this._monitor = Gio.VolumeMonitor.get();
 | 
						|
        this._monitor.connectObject(
 | 
						|
            'mount-added', (monitor, mount) => this._addMount(mount),
 | 
						|
            'mount-removed', (monitor, mount) => {
 | 
						|
                this._removeMount(mount);
 | 
						|
                this._updateMenuVisibility();
 | 
						|
            }, this);
 | 
						|
 | 
						|
        this._mounts = [];
 | 
						|
 | 
						|
        this._monitor.get_mounts().forEach(this._addMount.bind(this));
 | 
						|
 | 
						|
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 | 
						|
        this.menu.addAction(_('Open Files'), event => {
 | 
						|
            let appSystem = Shell.AppSystem.get_default();
 | 
						|
            let app = appSystem.lookup_app('org.gnome.Nautilus.desktop');
 | 
						|
            app.activate_full(-1, event.get_time());
 | 
						|
        });
 | 
						|
 | 
						|
        this._updateMenuVisibility();
 | 
						|
    }
 | 
						|
 | 
						|
    _updateMenuVisibility() {
 | 
						|
        if (this._mounts.filter(i => i.visible).length > 0)
 | 
						|
            this.show();
 | 
						|
        else
 | 
						|
            this.hide();
 | 
						|
    }
 | 
						|
 | 
						|
    _addMount(mount) {
 | 
						|
        let item = new MountMenuItem(mount);
 | 
						|
        this._mounts.unshift(item);
 | 
						|
        this.menu.addMenuItem(item, 0);
 | 
						|
 | 
						|
        item.connect('notify::visible', () => this._updateMenuVisibility());
 | 
						|
    }
 | 
						|
 | 
						|
    _removeMount(mount) {
 | 
						|
        for (let i = 0; i < this._mounts.length; i++) {
 | 
						|
            let item = this._mounts[i];
 | 
						|
            if (item.mount === mount) {
 | 
						|
                item.destroy();
 | 
						|
                this._mounts.splice(i, 1);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        log('Removing a mount that was never added to the menu');
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export default class PlaceMenuExtension extends Extension {
 | 
						|
    enable() {
 | 
						|
        this._indicator = new DriveMenu();
 | 
						|
        Main.panel.addToStatusArea('drive-menu', this._indicator);
 | 
						|
    }
 | 
						|
 | 
						|
    disable() {
 | 
						|
        this._indicator.destroy();
 | 
						|
        delete this._indicator;
 | 
						|
    }
 | 
						|
}
 |