New extension: Removable Drive Menu
Adds a menu in the system status area that tracks removable disk devices attached and offers to browse them and eject/unmount them. https://bugzilla.gnome.org/show_bug.cgi?id=647027
This commit is contained in:
+5
-3
@@ -21,8 +21,9 @@ GLIB_GSETTINGS
|
||||
ADDITIONAL_PACKAGES=
|
||||
|
||||
dnl keep this in sync with extensions/Makefile.am
|
||||
ALL_EXTENSIONS="example alternate-tab xrandr-indicator windowsNavigator auto-move-windows dock user-theme alternative-status-menu gajim"
|
||||
DEFAULT_EXTENSIONS="alternate-tab windowsNavigator dock alternative-status-menu"
|
||||
ALL_EXTENSIONS="example alternate-tab xrandr-indicator windowsNavigator auto-move-windows dock user-theme alternative-status-menu gajim drive-menu"
|
||||
AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS])
|
||||
DEFAULT_EXTENSIONS="alternate-tab windowsNavigator dock alternative-status-menu drive-menu"
|
||||
AC_ARG_ENABLE([extensions],
|
||||
[AS_HELP_STRING([--enable-extensions],[Space separated list of extensions to enable.
|
||||
The default is to build all extensions that can be installed in the home directory and have no external depedencies.
|
||||
@@ -42,7 +43,7 @@ for e in $enable_extensions; do
|
||||
ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"
|
||||
ADDITIONAL_PACKAGES="$ADDITIONAL_PAGKAGES gnome-desktop-3.0 >= 2.91.6"
|
||||
;;
|
||||
alternate-tab|example|windowsNavigator|auto-move-windows|dock|user-theme|alternative-status-menu|gajim)
|
||||
alternate-tab|example|windowsNavigator|auto-move-windows|dock|user-theme|alternative-status-menu|gajim|drive-menu)
|
||||
ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"
|
||||
;;
|
||||
*)
|
||||
@@ -65,6 +66,7 @@ AC_CONFIG_FILES([
|
||||
extensions/alternative-status-menu/Makefile
|
||||
extensions/auto-move-windows/Makefile
|
||||
extensions/dock/Makefile
|
||||
extensions/drive-menu/Makefile
|
||||
extensions/example/Makefile
|
||||
extensions/windowsNavigator/Makefile
|
||||
extensions/gajim/Makefile
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
DIST_SUBDIRS = example alternate-tab xrandr-indicator windowsNavigator auto-move-windows dock user-theme alternative-status-menu gajim
|
||||
DIST_SUBDIRS = $(ALL_EXTENSIONS)
|
||||
|
||||
SUBDIRS = $(ENABLED_EXTENSIONS)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
EXTENSION_ID = drive-menu
|
||||
|
||||
include ../../extension.mk
|
||||
@@ -0,0 +1,204 @@
|
||||
// Drive menu extension
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Panel = imports.ui.panel;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
function DriveMenuItem(drive) {
|
||||
this._init(drive);
|
||||
}
|
||||
|
||||
DriveMenuItem.prototype = {
|
||||
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
|
||||
|
||||
_init: function(drive) {
|
||||
PopupMenu.PopupBaseMenuItem.prototype._init.call(this);
|
||||
|
||||
this.label = new St.Label();
|
||||
this.addActor(this.label);
|
||||
|
||||
this.drive = drive;
|
||||
this._driveChangedId = this.drive.connect('changed', Lang.bind(this, this._updatePrimaryVolume));
|
||||
this._updatePrimaryVolume();
|
||||
|
||||
let ejectIcon = new St.Icon({ icon_name: 'media-eject',
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
style_class: 'popup-menu-icon ' });
|
||||
let ejectButton = new St.Button({ child: ejectIcon });
|
||||
ejectButton.connect('clicked', Lang.bind(this, this._eject));
|
||||
this.addActor(ejectButton);
|
||||
},
|
||||
|
||||
_updatePrimaryVolume: function() {
|
||||
// this should never fail, for the kind of GDrives we support
|
||||
this._volumes = this.drive.get_volumes();
|
||||
|
||||
if (this._volumes && this._volumes.length) {
|
||||
// any better idea, in case an external USB drive is partitioned?
|
||||
this._primaryVolume = this._volumes[0];
|
||||
this.label.text = this._primaryVolume.get_name();
|
||||
} else {
|
||||
this._primaryVolume = null;
|
||||
this.label.text = this.drive.get_name();
|
||||
}
|
||||
},
|
||||
|
||||
_eject: function() {
|
||||
if (this.drive.can_eject())
|
||||
this.drive.eject_with_operation(Gio.MountUnmountFlags.NONE,
|
||||
null, // Gio.MountOperation
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._ejectFinish));
|
||||
else
|
||||
this.drive.stop(Gio.MountUnmountFlags.NONE,
|
||||
null, // Gio.MountOperation
|
||||
null, // Gio.Cancellable
|
||||
Lang.bind(this, this._stopFinish));
|
||||
},
|
||||
|
||||
_stopFinish: function(drive, result) {
|
||||
try {
|
||||
drive.stop_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_ejectFinish: function(drive, result) {
|
||||
try {
|
||||
drive.eject_with_operation_finish(result);
|
||||
} catch(e) {
|
||||
this._reportFailure(e);
|
||||
}
|
||||
},
|
||||
|
||||
_reportFailure: function(exception) {
|
||||
let msg = _("Ejecting drive '%s' failed:").format(this.drive.get_name());
|
||||
Main.notifyError(msg, exception.message);
|
||||
},
|
||||
|
||||
_launchMount: function(mount) {
|
||||
let root = mount.get_root();
|
||||
// most of times will be nautilus, but it can change depending of volume contents
|
||||
let appInfo = root.query_default_handler(null);
|
||||
appInfo.launch([root], new Gio.AppLaunchContext());
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
if (this._driveChangedId) {
|
||||
this.drive.disconnect(this._driveChangedId);
|
||||
this._driveChangedId = 0;
|
||||
}
|
||||
|
||||
PopupMenu.PopupBaseMenuItem.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
let mount = this._primaryVolume.get_mount();
|
||||
if (mount) {
|
||||
this._launchMount(mount);
|
||||
} else {
|
||||
// try mounting the volume
|
||||
this._primaryVolume.mount(Gio.MountMountFlags.NONE, null, null, Lang.bind(this, function(volume, result) {
|
||||
try {
|
||||
volume.mount_finish(result);
|
||||
this._launchMount(volume.get_mount());
|
||||
} catch(e) {
|
||||
let msg = _("Mounting drive '%s' failed:").format(this.drive.get_name());
|
||||
Main.notifyError(msg, e.message);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
PopupMenu.PopupBaseMenuItem.prototype.activate.call(this, event);
|
||||
}
|
||||
};
|
||||
|
||||
function DriveMenu() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
DriveMenu.prototype = {
|
||||
__proto__: PanelMenu.SystemStatusButton.prototype,
|
||||
|
||||
_init: function() {
|
||||
// is 'media-eject' better?
|
||||
PanelMenu.SystemStatusButton.prototype._init.call(this, 'media-optical');
|
||||
|
||||
this._monitor = Gio.VolumeMonitor.get();
|
||||
this._monitor.connect('drive-connected', Lang.bind(this, function(monitor, drive) {
|
||||
this._addDrive(drive);
|
||||
this._updateMenuVisibility();
|
||||
}));
|
||||
this._monitor.connect('drive-disconnected', Lang.bind(this, function(monitor, drive) {
|
||||
this._removeDrive(drive);
|
||||
this._updateMenuVisibility();
|
||||
}));
|
||||
|
||||
this._drives = [ ];
|
||||
|
||||
this._monitor.get_connected_drives().forEach(Lang.bind(this, this._addDrive));
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Open file manager"), function(event) {
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let app = appSystem.get_app('nautilus.desktop');
|
||||
app.activate(-1);
|
||||
});
|
||||
|
||||
this._updateMenuVisibility();
|
||||
},
|
||||
|
||||
_isDriveInteresting: function(drive) {
|
||||
// We show only drives that are physically removable
|
||||
// (no network drives, no lvm/mdraid, no optical drives)
|
||||
return drive.can_stop() && drive.get_start_stop_type() == Gio.DriveStartStopType.SHUTDOWN;
|
||||
},
|
||||
|
||||
_addDrive: function(drive) {
|
||||
if (!this._isDriveInteresting(drive))
|
||||
return;
|
||||
|
||||
let item = new DriveMenuItem(drive);
|
||||
this._drives.unshift(item);
|
||||
this.menu.addMenuItem(item, 0);
|
||||
},
|
||||
|
||||
_removeDrive: function(drive) {
|
||||
if (!this._isDriveInteresting(drive))
|
||||
return;
|
||||
|
||||
for (let i = 0; i < this._drives.length; i++) {
|
||||
let item = this._drives[i];
|
||||
if (item.drive == drive) {
|
||||
item.destroy();
|
||||
this._drives.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
log ('Removing a drive that was never added to the menu');
|
||||
},
|
||||
|
||||
_updateMenuVisibility: function() {
|
||||
if (this._drives.length > 0)
|
||||
this.actor.show();
|
||||
else
|
||||
this.actor.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Put your extension initialization code here
|
||||
function main(metadata) {
|
||||
imports.gettext.bindtextdomain('gnome-shell-extensions', metadata.localedir);
|
||||
|
||||
Panel.STANDARD_TRAY_ICON_ORDER.unshift('drive-menu');
|
||||
Panel.STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['drive-menu'] = DriveMenu;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"uuid": "@uuid@",
|
||||
"name": "Removable Drive Menu",
|
||||
"description": "A status menu for accessing and unmounting removable devices",
|
||||
"shell-version": [ "@shell_current@" ],
|
||||
"localedir": "@LOCALEDIR@",
|
||||
"url": "@url@"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* This extensions requires no custom styling */
|
||||
Reference in New Issue
Block a user