[gnome-shell-extensions] New extension: Removable Drive Menu



commit 2df002955e829cdc2993a3c83ad82d4443ceab4f
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Thu Apr 7 19:02:18 2011 +0200

    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

 configure.ac                           |    8 +-
 extensions/Makefile.am                 |    2 +-
 extensions/drive-menu/Makefile.am      |    3 +
 extensions/drive-menu/extension.js     |  204 ++++++++++++++++++++++++++++++++
 extensions/drive-menu/metadata.json.in |    8 ++
 extensions/drive-menu/stylesheet.css   |    1 +
 6 files changed, 222 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 124ab46..2e66abc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index f910a10..4e8c55a 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -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)
diff --git a/extensions/drive-menu/Makefile.am b/extensions/drive-menu/Makefile.am
new file mode 100644
index 0000000..af90471
--- /dev/null
+++ b/extensions/drive-menu/Makefile.am
@@ -0,0 +1,3 @@
+EXTENSION_ID = drive-menu
+
+include ../../extension.mk
diff --git a/extensions/drive-menu/extension.js b/extensions/drive-menu/extension.js
new file mode 100644
index 0000000..a5882d3
--- /dev/null
+++ b/extensions/drive-menu/extension.js
@@ -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;
+}
diff --git a/extensions/drive-menu/metadata.json.in b/extensions/drive-menu/metadata.json.in
new file mode 100644
index 0000000..517bc56
--- /dev/null
+++ b/extensions/drive-menu/metadata.json.in
@@ -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@"
+}
diff --git a/extensions/drive-menu/stylesheet.css b/extensions/drive-menu/stylesheet.css
new file mode 100644
index 0000000..fbe5640
--- /dev/null
+++ b/extensions/drive-menu/stylesheet.css
@@ -0,0 +1 @@
+/* This extensions requires no custom styling */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]