[gvfs] Bug 585591 – Starting/stopping drives



commit 4053577a19585c63e23e6c6e85011e2df9910745
Author: David Zeuthen <davidz redhat com>
Date:   Wed Jun 17 09:49:44 2009 -0400

    Bug 585591 â?? Starting/stopping drives
    
    This is the GVfs implementation for the new GIO API for
    starting/stopping drives. See
    
    http://bugzilla.gnome.org/show_bug.cgi?id=585591
    
    for details.

 client/gdaemonfile.c                         |   93 ++++
 client/gvfsdaemondbus.c                      |   15 +-
 common/gmountoperationdbus.c                 |   12 +-
 common/gmountsource.c                        |    6 +-
 common/gvfsdaemonprotocol.h                  |    2 +
 daemon/Makefile.am                           |    2 +
 daemon/gvfsbackend.c                         |   10 +
 daemon/gvfsbackend.h                         |   19 +
 daemon/gvfsbackendcomputer.c                 |  162 ++++++-
 daemon/gvfsjobstartmountable.c               |  168 +++++++
 daemon/gvfsjobstartmountable.h               |   64 +++
 daemon/gvfsjobstopmountable.c                |  164 +++++++
 daemon/gvfsjobstopmountable.h                |   64 +++
 monitor/gdu/ggdudrive.c                      |  676 +++++++++++++++++++++-----
 monitor/gdu/ggduvolumemonitor.c              |   57 ++-
 monitor/proxy/gproxydrive.c                  |  626 ++++++++++++++++++++++++-
 monitor/proxy/gproxydrive.h                  |    9 +
 monitor/proxy/gproxyvolume.c                 |    2 +-
 monitor/proxy/gproxyvolumemonitor.c          |   33 ++-
 monitor/proxy/gproxyvolumemonitor.h          |    8 +
 monitor/proxy/gvfsproxyvolumemonitordaemon.c |  555 +++++++++++++++++++++-
 programs/gvfs-mount.c                        |   16 +-
 22 files changed, 2587 insertions(+), 176 deletions(-)
---
diff --git a/client/gdaemonfile.c b/client/gdaemonfile.c
index 0071a5b..2da81f4 100644
--- a/client/gdaemonfile.c
+++ b/client/gdaemonfile.c
@@ -1326,6 +1326,95 @@ g_daemon_file_mount_mountable_finish (GFile               *file,
 }
 
 static void
+start_mountable_async_cb (DBusMessage *reply,
+			  DBusConnection *connection,
+			  GSimpleAsyncResult *result,
+			  GCancellable *cancellable,
+			  gpointer callback_data)
+{
+  g_simple_async_result_complete (result);
+}
+
+static void
+g_daemon_file_start_mountable (GFile               *file,
+			       GDriveStartFlags     flags,
+			       GMountOperation     *start_operation,
+			       GCancellable        *cancellable,
+			       GAsyncReadyCallback  callback,
+			       gpointer             user_data)
+{
+  GMountSource *mount_source;
+  const char *dbus_id, *obj_path;
+
+  mount_source = g_mount_operation_dbus_wrap (start_operation, _g_daemon_vfs_get_async_bus ());
+
+  dbus_id = g_mount_source_get_dbus_id (mount_source);
+  obj_path = g_mount_source_get_obj_path (mount_source);
+
+  if (start_operation)
+    g_object_ref (start_operation);
+
+  do_async_path_call (file,
+		      G_VFS_DBUS_MOUNT_OP_START_MOUNTABLE,
+		      cancellable,
+		      callback, user_data,
+		      start_mountable_async_cb,
+		      start_operation, start_operation ? g_object_unref : NULL,
+		      DBUS_TYPE_STRING, &dbus_id,
+		      DBUS_TYPE_OBJECT_PATH, &obj_path,
+		      0);
+
+  g_object_unref (mount_source);
+}
+
+static gboolean
+g_daemon_file_start_mountable_finish (GFile               *file,
+				      GAsyncResult        *result,
+				      GError             **error)
+{
+  return TRUE;
+}
+
+static void
+stop_mountable_async_cb (DBusMessage *reply,
+                         DBusConnection *connection,
+                         GSimpleAsyncResult *result,
+                         GCancellable *cancellable,
+                         gpointer callback_data)
+{
+  g_simple_async_result_complete (result);
+}
+
+static void
+g_daemon_file_stop_mountable (GFile               *file,
+                              GMountUnmountFlags   flags,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  guint32 dbus_flags;
+
+  dbus_flags = flags;
+  do_async_path_call (file,
+		      G_VFS_DBUS_MOUNT_OP_STOP_MOUNTABLE,
+		      cancellable,
+		      callback, user_data,
+		      stop_mountable_async_cb,
+		      NULL, NULL,
+		      DBUS_TYPE_UINT32, &dbus_flags,
+		      0);
+}
+
+static gboolean
+g_daemon_file_stop_mountable_finish (GFile               *file,
+                                     GAsyncResult        *result,
+                                     GError             **error)
+{
+  return TRUE;
+}
+
+
+static void
 eject_mountable_async_cb (DBusMessage *reply,
 			  DBusConnection *connection,
 			  GSimpleAsyncResult *result,
@@ -2787,6 +2876,10 @@ g_daemon_file_file_iface_init (GFileIface *iface)
   iface->make_symbolic_link = g_daemon_file_make_symbolic_link;
   iface->monitor_dir = g_daemon_file_monitor_dir;
   iface->monitor_file = g_daemon_file_monitor_file;
+  iface->start_mountable = g_daemon_file_start_mountable;
+  iface->start_mountable_finish = g_daemon_file_start_mountable_finish;
+  iface->stop_mountable = g_daemon_file_stop_mountable;
+  iface->stop_mountable_finish = g_daemon_file_stop_mountable_finish;
 
   /* Async operations */
 
diff --git a/client/gvfsdaemondbus.c b/client/gvfsdaemondbus.c
index c3df2cf..a9dae13 100644
--- a/client/gvfsdaemondbus.c
+++ b/client/gvfsdaemondbus.c
@@ -508,10 +508,19 @@ async_call_send (AsyncDBusCall *async_call)
 {
   AsyncCallCancelData *cancel_data;
 
+  _g_dbus_connection_call_async (async_call->connection,
+				 async_call->message,
+				 G_VFS_DBUS_TIMEOUT_MSECS,
+				 async_dbus_response,
+				 async_call);
+
   if (async_call->cancellable)
     {
       cancel_data = g_new0 (AsyncCallCancelData, 1);
       cancel_data->connection = dbus_connection_ref (async_call->connection);
+      /* make sure we get the serial *after* the message has been sent, otherwise
+       * it will be 0
+       */
       cancel_data->serial = dbus_message_get_serial (async_call->message);
       async_call->cancelled_tag =
 	g_signal_connect_data (async_call->cancellable, "cancelled",
@@ -520,12 +529,6 @@ async_call_send (AsyncDBusCall *async_call)
 			       (GClosureNotify)async_call_cancel_data_free,
 			       0);
     }
-
-  _g_dbus_connection_call_async (async_call->connection,
-				 async_call->message,
-				 G_VFS_DBUS_TIMEOUT_MSECS,
-				 async_dbus_response,
-				 async_call);
 }
 
 static void
diff --git a/common/gmountoperationdbus.c b/common/gmountoperationdbus.c
index 7700c07..b8ac4f7 100644
--- a/common/gmountoperationdbus.c
+++ b/common/gmountoperationdbus.c
@@ -281,10 +281,8 @@ mount_op_ask_question (GMountOperationDBus *op_dbus,
   const char *message_string;
   char **choices;
   int num_choices;
-  dbus_bool_t handled = FALSE;
   DBusMessage *reply;
   DBusError error;
-  gboolean res;
   DBusMessageIter iter;
 
   reply = NULL;
@@ -316,15 +314,7 @@ mount_op_ask_question (GMountOperationDBus *op_dbus,
 
   g_signal_emit_by_name (op_dbus->op, "ask_question",
 			 message_string,
-			 choices,
-			 &res);
-  if (!res)
-    {
-      _g_dbus_message_append_args (reply,
-				   DBUS_TYPE_BOOLEAN, &handled,
-				   0);
-      mount_op_send_reply (op_dbus, reply);
-    }
+			 choices);
   
   dbus_free_string_array (choices);
 }
diff --git a/common/gmountsource.c b/common/gmountsource.c
index b2f490d..ac705fa 100644
--- a/common/gmountsource.c
+++ b/common/gmountsource.c
@@ -613,7 +613,7 @@ g_mount_source_ask_question_async (GMountSource       *source,
   _g_dbus_message_append_args (message,
 			       DBUS_TYPE_STRING, &message_string,
 			       DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
-			       choices, n_choices,
+			       &choices, n_choices,
 			       0);
 
   result = g_simple_async_result_new (G_OBJECT (source), callback, user_data, 
@@ -687,13 +687,12 @@ static gboolean
 op_ask_question (GMountOperation *op,
 		 const char      *message,
 		 const char     **choices,
-		 gint             n_choices,
 		 GMountSource    *mount_source)
 {
   g_mount_source_ask_question_async (mount_source,
 				     message,
 				     choices,
-				     n_choices,
+				     g_strv_length ((gchar **) choices),
 				     op_ask_question_reply,
 				     g_object_ref (op));
   g_signal_stop_emission_by_name (op, "ask_question");
@@ -741,7 +740,6 @@ g_mount_source_get_operation (GMountSource *mount_source)
 			  g_object_ref (mount_source),
 			  g_object_unref);
 
-
   g_signal_connect (op, "ask_password", (GCallback)op_ask_password, mount_source);
   g_signal_connect (op, "ask_question", (GCallback)op_ask_question, mount_source);
   g_signal_connect (op, "aborted", (GCallback)op_aborted, mount_source);
diff --git a/common/gvfsdaemonprotocol.h b/common/gvfsdaemonprotocol.h
index 5f70ed3..ce8b11e 100644
--- a/common/gvfsdaemonprotocol.h
+++ b/common/gvfsdaemonprotocol.h
@@ -37,6 +37,8 @@ G_BEGIN_DECLS
 #define G_VFS_DBUS_MOUNT_OP_MOUNT_MOUNTABLE "MountMountable"
 #define G_VFS_DBUS_MOUNT_OP_UNMOUNT_MOUNTABLE "UnountMountable"
 #define G_VFS_DBUS_MOUNT_OP_EJECT_MOUNTABLE "EjectMountable"
+#define G_VFS_DBUS_MOUNT_OP_START_MOUNTABLE "StartMountable"
+#define G_VFS_DBUS_MOUNT_OP_STOP_MOUNTABLE "StopMountable"
 #define G_VFS_DBUS_MOUNT_OP_SET_DISPLAY_NAME "SetDisplayName"
 #define G_VFS_DBUS_MOUNT_OP_DELETE "Delete"
 #define G_VFS_DBUS_MOUNT_OP_TRASH "Trash"
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 9761eff..ae24771 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -124,6 +124,8 @@ libdaemon_la_SOURCES = \
 	gvfsjobunmount.c gvfsjobunmount.h \
 	gvfsjobmountmountable.c gvfsjobmountmountable.h \
 	gvfsjobunmountmountable.c gvfsjobunmountmountable.h \
+	gvfsjobstartmountable.c gvfsjobstartmountable.h \
+	gvfsjobstopmountable.c gvfsjobstopmountable.h \
 	gvfsjobopenforread.c gvfsjobopenforread.h \
 	gvfsjobopeniconforread.c gvfsjobopeniconforread.h \
 	gvfsjobread.c gvfsjobread.h \
diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c
index 74aabdb..3be3cdb 100644
--- a/daemon/gvfsbackend.c
+++ b/daemon/gvfsbackend.c
@@ -46,6 +46,8 @@
 #include <gvfsjobunmount.h>
 #include <gvfsjobmountmountable.h>
 #include <gvfsjobunmountmountable.h>
+#include <gvfsjobstartmountable.h>
+#include <gvfsjobstopmountable.h>
 #include <gvfsjobmakedirectory.h>
 #include <gvfsjobmakesymlink.h>
 #include <gvfsjobcreatemonitor.h>
@@ -533,6 +535,14 @@ backend_dbus_handler (DBusConnection  *connection,
     job = g_vfs_job_unmount_mountable_new (connection, message, backend, TRUE);
   else if (dbus_message_is_method_call (message,
 					G_VFS_DBUS_MOUNT_INTERFACE,
+					G_VFS_DBUS_MOUNT_OP_START_MOUNTABLE))
+    job = g_vfs_job_start_mountable_new (connection, message, backend);
+  else if (dbus_message_is_method_call (message,
+					G_VFS_DBUS_MOUNT_INTERFACE,
+					G_VFS_DBUS_MOUNT_OP_STOP_MOUNTABLE))
+    job = g_vfs_job_stop_mountable_new (connection, message, backend);
+  else if (dbus_message_is_method_call (message,
+					G_VFS_DBUS_MOUNT_INTERFACE,
 					G_VFS_DBUS_MOUNT_OP_SET_DISPLAY_NAME))
     job = g_vfs_job_set_display_name_new (connection, message, backend);
   else if (dbus_message_is_method_call (message,
diff --git a/daemon/gvfsbackend.h b/daemon/gvfsbackend.h
index f2bed48..ee68bd8 100644
--- a/daemon/gvfsbackend.h
+++ b/daemon/gvfsbackend.h
@@ -47,6 +47,8 @@ typedef struct _GVfsJobMount            GVfsJobMount;
 typedef struct _GVfsJobUnmount          GVfsJobUnmount;
 typedef struct _GVfsJobMountMountable   GVfsJobMountMountable;
 typedef struct _GVfsJobUnmountMountable GVfsJobUnmountMountable;
+typedef struct _GVfsJobStartMountable   GVfsJobStartMountable;
+typedef struct _GVfsJobStopMountable    GVfsJobStopMountable;
 typedef struct _GVfsJobOpenForRead      GVfsJobOpenForRead;
 typedef struct _GVfsJobOpenIconForRead  GVfsJobOpenIconForRead;
 typedef struct _GVfsJobSeekRead         GVfsJobSeekRead;
@@ -415,6 +417,23 @@ struct _GVfsBackendClass
   gboolean (*try_query_writable_namespaces) (GVfsBackend *backend,
 					     GVfsJobQueryAttributes *job,
 					     const char *filename);
+
+  void     (*start_mountable)   (GVfsBackend *backend,
+				 GVfsJobStartMountable *job,
+				 const char *filename,
+				 GMountSource *mount_source);
+  gboolean (*try_start_mountable) (GVfsBackend *backend,
+                                   GVfsJobStartMountable *job,
+                                   const char *filename,
+                                   GMountSource *mount_source);
+  void     (*stop_mountable) (GVfsBackend *backend,
+                              GVfsJobStopMountable *job,
+                              const char *filename,
+                              GMountUnmountFlags flags);
+  gboolean (*try_stop_mountable)   (GVfsBackend *backend,
+				    GVfsJobStopMountable *job,
+				    const char *filename,
+				    GMountUnmountFlags flags);
 };
 
 GType g_vfs_backend_get_type (void) G_GNUC_CONST;
diff --git a/daemon/gvfsbackendcomputer.c b/daemon/gvfsbackendcomputer.c
index c0d355f..9783df6 100644
--- a/daemon/gvfsbackendcomputer.c
+++ b/daemon/gvfsbackendcomputer.c
@@ -21,7 +21,6 @@
  *          Cosimo Cecchi <cosimoc gnome org>
  */
 
-
 #include <config.h>
 
 #include <sys/types.h>
@@ -66,6 +65,9 @@ typedef struct {
   gboolean can_mount;
   gboolean can_unmount;
   gboolean can_eject;
+  gboolean can_start;
+  gboolean can_stop;
+  GDriveStartStopType start_stop_type;
   
   GDrive *drive;
   GVolume *volume;
@@ -133,7 +135,10 @@ computer_file_equal (ComputerFile *a,
 
   if (a->can_mount != b->can_mount ||
       a->can_unmount != b->can_unmount ||
-      a->can_eject != b->can_eject)
+      a->can_eject != b->can_eject ||
+      a->can_start != b->can_start ||
+      a->can_stop != b->can_stop ||
+      a->start_stop_type != b->start_stop_type)
     return FALSE;
 
   return TRUE;
@@ -458,6 +463,11 @@ recompute_files (GVfsBackendComputer *backend)
 
       if (file->drive)
         {
+          file->can_start = g_drive_can_start (file->drive);
+          file->can_stop = g_drive_can_stop (file->drive);
+          file->start_stop_type = g_drive_get_start_stop_type (file->drive);
+          if (file->can_start)
+            file->can_mount = FALSE;
           basename = g_drive_get_name (file->drive);
           extension = ".drive";
         }
@@ -650,6 +660,9 @@ file_info_from_file (ComputerFile *file,
   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT, file->can_mount);
   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT, file->can_unmount);
   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT, file->can_eject);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START, file->can_start);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP, file->can_stop);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE, file->start_stop_type);
 
   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
@@ -781,8 +794,6 @@ mount_volume_cb (GObject *source_object,
   
   volume = G_VOLUME (source_object);
 
-  /* TODO: We're leaking the GMountOperation here */
-  
   error = NULL;
   if (g_volume_mount_finish (volume, res, &error))
     {
@@ -910,6 +921,8 @@ try_mount_mountable (GVfsBackend *backend,
       if (file->volume)
         {
           mount_op = g_mount_source_get_operation (mount_source);
+          /* free mount_op when job is completed */
+          g_object_set_data_full (G_OBJECT (job), "gvfs-backend-computer-mount-op", mount_op, g_object_unref);
           g_volume_mount (file->volume,
                           0,
                           mount_op,
@@ -1133,6 +1146,145 @@ try_eject_mountable (GVfsBackend *backend,
   return TRUE;
 }
 
+
+static void
+drive_start_cb (GObject *source_object,
+                GAsyncResult *res,
+                gpointer user_data)
+{
+  GVfsJobStartMountable *job = user_data;
+  GError *error;
+  GDrive *drive;
+
+  drive = G_DRIVE (source_object);
+
+  error = NULL;
+  if (g_drive_start_finish (drive, res, &error))
+    {
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+  else
+    {
+      g_vfs_job_failed_from_error  (G_VFS_JOB (job), error);
+      g_error_free (error);
+    }
+}
+
+static gboolean
+try_start_mountable (GVfsBackend *backend,
+                     GVfsJobStartMountable *job,
+                     const char *filename,
+                     GMountSource *mount_source)
+{
+  ComputerFile *file;
+  GMountOperation *start_op;
+
+  file = lookup (G_VFS_BACKEND_COMPUTER (backend),
+                 G_VFS_JOB (job), filename);
+
+  if (file == &root)
+    {
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_NOT_MOUNTABLE_FILE,
+                        _("Not a mountable file"));
+    }
+  else if (file != NULL)
+    {
+      if (file->drive != NULL)
+        {
+          start_op = g_mount_source_get_operation (mount_source);
+          /* free start_op when job is completed */
+          g_object_set_data_full (G_OBJECT (job), "gvfs-backend-computer-start-op", start_op, g_object_unref);
+          g_drive_start (file->drive,
+                         0,
+                         start_op,
+                         G_VFS_JOB (job)->cancellable,
+                         drive_start_cb,
+                         job);
+        }
+      else
+        {
+          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                            G_IO_ERROR_NOT_SUPPORTED,
+                            _("Can't start file"));
+        }
+    }
+  else
+    {
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_NOT_SUPPORTED,
+                        _("Can't start file"));
+    }
+  return TRUE;
+}
+
+
+static void
+drive_stop_cb (GObject *source_object,
+               GAsyncResult *res,
+               gpointer user_data)
+{
+  GVfsJobStopMountable *job = user_data;
+  GError *error;
+  GDrive *drive;
+
+  drive = G_DRIVE (source_object);
+
+  error = NULL;
+  if (g_drive_stop_finish (drive, res, &error))
+    {
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+  else
+    {
+      g_vfs_job_failed_from_error  (G_VFS_JOB (job), error);
+      g_error_free (error);
+    }
+}
+
+static gboolean
+try_stop_mountable (GVfsBackend *backend,
+                    GVfsJobStopMountable *job,
+                    const char *filename,
+                    GMountUnmountFlags flags)
+{
+  ComputerFile *file;
+
+  file = lookup (G_VFS_BACKEND_COMPUTER (backend),
+                 G_VFS_JOB (job), filename);
+
+  if (file == &root)
+    {
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_NOT_MOUNTABLE_FILE,
+                        _("Not a mountable file"));
+    }
+  else if (file != NULL)
+    {
+      if (file->drive != NULL)
+        {
+          g_drive_stop (file->drive,
+                        flags,
+                        G_VFS_JOB (job)->cancellable,
+                        drive_stop_cb,
+                        job);
+        }
+      else
+        {
+          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                            G_IO_ERROR_NOT_SUPPORTED,
+                            _("Can't stop file"));
+        }
+    }
+  else
+    {
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_NOT_SUPPORTED,
+                        _("Can't stop file"));
+    }
+  return TRUE;
+}
+
 static void
 g_vfs_backend_computer_class_init (GVfsBackendComputerClass *klass)
 {
@@ -1149,4 +1301,6 @@ g_vfs_backend_computer_class_init (GVfsBackendComputerClass *klass)
   backend_class->try_mount_mountable = try_mount_mountable;
   backend_class->try_unmount_mountable = try_unmount_mountable;
   backend_class->try_eject_mountable = try_eject_mountable;
+  backend_class->try_start_mountable = try_start_mountable;
+  backend_class->try_stop_mountable = try_stop_mountable;
 }
diff --git a/daemon/gvfsjobstartmountable.c b/daemon/gvfsjobstartmountable.c
new file mode 100644
index 0000000..508f7f9
--- /dev/null
+++ b/daemon/gvfsjobstartmountable.c
@@ -0,0 +1,168 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <glib/gi18n.h>
+#include "gvfsjobstartmountable.h"
+#include "gdbusutils.h"
+#include "gvfsdaemonutils.h"
+
+G_DEFINE_TYPE (GVfsJobStartMountable, g_vfs_job_start_mountable, G_VFS_TYPE_JOB_DBUS)
+
+static void         run          (GVfsJob        *job);
+static gboolean     try          (GVfsJob        *job);
+static DBusMessage *create_reply (GVfsJob        *job,
+				  DBusConnection *connection,
+				  DBusMessage    *message);
+
+static void
+g_vfs_job_start_mountable_finalize (GObject *object)
+{
+  GVfsJobStartMountable *job;
+
+  job = G_VFS_JOB_START_MOUNTABLE (object);
+
+  if (job->mount_source)
+    g_object_unref (job->mount_source);
+
+  g_free (job->filename);
+
+  if (G_OBJECT_CLASS (g_vfs_job_start_mountable_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_job_start_mountable_parent_class)->finalize) (object);
+}
+
+static void
+g_vfs_job_start_mountable_class_init (GVfsJobStartMountableClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsJobClass *job_class = G_VFS_JOB_CLASS (klass);
+  GVfsJobDBusClass *job_dbus_class = G_VFS_JOB_DBUS_CLASS (klass);
+
+  gobject_class->finalize = g_vfs_job_start_mountable_finalize;
+  job_class->run = run;
+  job_class->try = try;
+  job_dbus_class->create_reply = create_reply;
+}
+
+static void
+g_vfs_job_start_mountable_init (GVfsJobStartMountable *job)
+{
+}
+
+GVfsJob *
+g_vfs_job_start_mountable_new (DBusConnection *connection,
+			       DBusMessage *message,
+			       GVfsBackend *backend)
+{
+  GVfsJobStartMountable *job;
+  DBusMessage *reply;
+  DBusMessageIter iter;
+  DBusError derror;
+  char *path;
+  const char *dbus_id, *obj_path;
+
+  dbus_error_init (&derror);
+  dbus_message_iter_init (message, &iter);
+
+  path = NULL;
+  if (!_g_dbus_message_iter_get_args (&iter, &derror,
+				      G_DBUS_TYPE_CSTRING, &path,
+				      DBUS_TYPE_STRING, &dbus_id,
+				      DBUS_TYPE_OBJECT_PATH, &obj_path,
+				      0))
+    {
+      g_free (path);
+      reply = dbus_message_new_error (message,
+				      derror.name,
+                                      derror.message);
+      dbus_error_free (&derror);
+
+      dbus_connection_send (connection, reply, NULL);
+      return NULL;
+    }
+
+  job = g_object_new (G_VFS_TYPE_JOB_START_MOUNTABLE,
+		      "message", message,
+		      "connection", connection,
+		      NULL);
+
+  job->filename = path;
+  job->backend = backend;
+  job->mount_source = g_mount_source_new (dbus_id, obj_path);
+
+  return G_VFS_JOB (job);
+}
+
+static void
+run (GVfsJob *job)
+{
+  GVfsJobStartMountable *op_job = G_VFS_JOB_START_MOUNTABLE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->start_mountable == NULL)
+    {
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+			_("Operation not supported by backend"));
+      return;
+    }
+
+  class->start_mountable (op_job->backend,
+			  op_job,
+			  op_job->filename,
+			  op_job->mount_source);
+}
+
+static gboolean
+try (GVfsJob *job)
+{
+  GVfsJobStartMountable *op_job = G_VFS_JOB_START_MOUNTABLE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->try_start_mountable == NULL)
+    return FALSE;
+
+  return class->try_start_mountable (op_job->backend,
+				     op_job,
+				     op_job->filename,
+				     op_job->mount_source);
+}
+
+/* Might be called on an i/o thread */
+static DBusMessage *
+create_reply (GVfsJob *job,
+	      DBusConnection *connection,
+	      DBusMessage *message)
+{
+  DBusMessage *reply;
+
+  reply = dbus_message_new_method_return (message);
+
+  return reply;
+}
diff --git a/daemon/gvfsjobstartmountable.h b/daemon/gvfsjobstartmountable.h
new file mode 100644
index 0000000..95167f7
--- /dev/null
+++ b/daemon/gvfsjobstartmountable.h
@@ -0,0 +1,64 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#ifndef __G_VFS_JOB_START_MOUNTABLE_H__
+#define __G_VFS_JOB_START_MOUNTABLE_H__
+
+#include <gio/gio.h>
+#include <gvfsjob.h>
+#include <gvfsjobdbus.h>
+#include <gvfsbackend.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_JOB_START_MOUNTABLE         (g_vfs_job_start_mountable_get_type ())
+#define G_VFS_JOB_START_MOUNTABLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_JOB_START_MOUNTABLE, GVfsJobStartMountable))
+#define G_VFS_JOB_START_MOUNTABLE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_JOB_START_MOUNTABLE, GVfsJobStartMountableClass))
+#define G_VFS_IS_JOB_START_MOUNTABLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_JOB_START_MOUNTABLE))
+#define G_VFS_IS_JOB_START_MOUNTABLE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_JOB_START_MOUNTABLE))
+#define G_VFS_JOB_START_MOUNTABLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_JOB_START_MOUNTABLE, GVfsJobStartMountableClass))
+
+typedef struct _GVfsJobStartMountableClass   GVfsJobStartMountableClass;
+
+struct _GVfsJobStartMountable
+{
+  GVfsJobDBus parent_instance;
+
+  GVfsBackend *backend;
+  char *filename;
+  GMountSource *mount_source;
+};
+
+struct _GVfsJobStartMountableClass
+{
+  GVfsJobDBusClass parent_class;
+};
+
+GType g_vfs_job_start_mountable_get_type (void) G_GNUC_CONST;
+
+GVfsJob *g_vfs_job_start_mountable_new        (DBusConnection        *connection,
+					       DBusMessage           *message,
+					       GVfsBackend           *backend);
+
+G_END_DECLS
+
+#endif /* __G_VFS_JOB_START_MOUNTABLE_H__ */
diff --git a/daemon/gvfsjobstopmountable.c b/daemon/gvfsjobstopmountable.c
new file mode 100644
index 0000000..63df2af
--- /dev/null
+++ b/daemon/gvfsjobstopmountable.c
@@ -0,0 +1,164 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <glib/gi18n.h>
+#include "gvfsjobstopmountable.h"
+#include "gdbusutils.h"
+#include "gvfsdaemonutils.h"
+
+G_DEFINE_TYPE (GVfsJobStopMountable, g_vfs_job_stop_mountable, G_VFS_TYPE_JOB_DBUS)
+
+static void         run          (GVfsJob        *job);
+static gboolean     try          (GVfsJob        *job);
+static DBusMessage *create_reply (GVfsJob        *job,
+				  DBusConnection *connection,
+				  DBusMessage    *message);
+
+static void
+g_vfs_job_stop_mountable_finalize (GObject *object)
+{
+  GVfsJobStopMountable *job;
+
+  job = G_VFS_JOB_STOP_MOUNTABLE (object);
+
+  g_free (job->filename);
+  
+  if (G_OBJECT_CLASS (g_vfs_job_stop_mountable_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_job_stop_mountable_parent_class)->finalize) (object);
+}
+
+static void
+g_vfs_job_stop_mountable_class_init (GVfsJobStopMountableClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsJobClass *job_class = G_VFS_JOB_CLASS (klass);
+  GVfsJobDBusClass *job_dbus_class = G_VFS_JOB_DBUS_CLASS (klass);
+  
+  gobject_class->finalize = g_vfs_job_stop_mountable_finalize;
+  job_class->run = run;
+  job_class->try = try;
+  job_dbus_class->create_reply = create_reply;
+}
+
+static void
+g_vfs_job_stop_mountable_init (GVfsJobStopMountable *job)
+{
+}
+
+GVfsJob *
+g_vfs_job_stop_mountable_new (DBusConnection *connection,
+                              DBusMessage *message,
+                              GVfsBackend *backend)
+{
+  GVfsJobStopMountable *job;
+  DBusMessage *reply;
+  DBusMessageIter iter;
+  DBusError derror;
+  char *path;
+  guint32 flags;
+  
+  dbus_error_init (&derror);
+  dbus_message_iter_init (message, &iter);
+
+  path = NULL;
+  if (!_g_dbus_message_iter_get_args (&iter, &derror, 
+				      G_DBUS_TYPE_CSTRING, &path,
+				      DBUS_TYPE_UINT32, &flags,
+				      0))
+    {
+      g_free (path);
+      reply = dbus_message_new_error (message,
+				      derror.name,
+                                      derror.message);
+      dbus_error_free (&derror);
+
+      dbus_connection_send (connection, reply, NULL);
+      return NULL;
+    }
+
+  job = g_object_new (G_VFS_TYPE_JOB_STOP_MOUNTABLE,
+		      "message", message,
+		      "connection", connection,
+		      NULL);
+
+  job->filename = path;
+  job->backend = backend;
+  job->flags = flags;
+  
+  return G_VFS_JOB (job);
+}
+
+static void
+run (GVfsJob *job)
+{
+  GVfsJobStopMountable *op_job = G_VFS_JOB_STOP_MOUNTABLE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->stop_mountable == NULL)
+    {
+      g_vfs_job_failed (job, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                        _("Operation not supported by backend"));
+      return;
+    }
+
+  class->stop_mountable (op_job->backend,
+                         op_job,
+                         op_job->filename,
+                         op_job->flags);
+}
+
+static gboolean
+try (GVfsJob *job)
+{
+  GVfsJobStopMountable *op_job = G_VFS_JOB_STOP_MOUNTABLE (job);
+  GVfsBackendClass *class = G_VFS_BACKEND_GET_CLASS (op_job->backend);
+
+  if (class->try_stop_mountable == NULL)
+    return FALSE;
+
+  return class->try_stop_mountable (op_job->backend,
+                                    op_job,
+                                    op_job->filename,
+                                    op_job->flags);
+}
+
+/* Might be called on an i/o thread */
+static DBusMessage *
+create_reply (GVfsJob *job,
+	      DBusConnection *connection,
+	      DBusMessage *message)
+{
+  DBusMessage *reply;
+
+  reply = dbus_message_new_method_return (message);
+  
+  return reply;
+}
diff --git a/daemon/gvfsjobstopmountable.h b/daemon/gvfsjobstopmountable.h
new file mode 100644
index 0000000..e584ce5
--- /dev/null
+++ b/daemon/gvfsjobstopmountable.h
@@ -0,0 +1,64 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+#ifndef __G_VFS_JOB_STOP_MOUNTABLE_H__
+#define __G_VFS_JOB_STOP_MOUNTABLE_H__
+
+#include <gio/gio.h>
+#include <gvfsjob.h>
+#include <gvfsjobdbus.h>
+#include <gvfsbackend.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_JOB_STOP_MOUNTABLE         (g_vfs_job_stop_mountable_get_type ())
+#define G_VFS_JOB_STOP_MOUNTABLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_JOB_STOP_MOUNTABLE, GVfsJobStopMountable))
+#define G_VFS_JOB_STOP_MOUNTABLE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_JOB_STOP_MOUNTABLE, GVfsJobStopMountableClass))
+#define G_VFS_IS_JOB_STOP_MOUNTABLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_JOB_STOP_MOUNTABLE))
+#define G_VFS_IS_JOB_STOP_MOUNTABLE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_JOB_STOP_MOUNTABLE))
+#define G_VFS_JOB_STOP_MOUNTABLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_JOB_STOP_MOUNTABLE, GVfsJobStopMountableClass))
+
+typedef struct _GVfsJobStopMountableClass   GVfsJobStopMountableClass;
+
+struct _GVfsJobStopMountable
+{
+  GVfsJobDBus parent_instance;
+
+  GVfsBackend *backend;
+  char *filename;
+  GMountUnmountFlags flags;
+};
+
+struct _GVfsJobStopMountableClass
+{
+  GVfsJobDBusClass parent_class;
+};
+
+GType g_vfs_job_stop_mountable_get_type (void) G_GNUC_CONST;
+
+GVfsJob *g_vfs_job_stop_mountable_new        (DBusConnection        *connection,
+                                              DBusMessage           *message,
+                                              GVfsBackend           *backend);
+
+G_END_DECLS
+
+#endif /* __G_VFS_JOB_STOP_MOUNTABLE_H__ */
diff --git a/monitor/gdu/ggdudrive.c b/monitor/gdu/ggdudrive.c
index d332e14..c89928c 100644
--- a/monitor/gdu/ggdudrive.c
+++ b/monitor/gdu/ggdudrive.c
@@ -51,6 +51,10 @@ struct _GGduDrive {
   gboolean can_eject;
   gboolean can_poll_for_media;
   gboolean is_media_check_automatic;
+
+  GDriveStartStopType start_stop_type;
+  gboolean can_start;
+  gboolean can_stop;
 };
 
 static void g_gdu_drive_drive_iface_init (GDriveIface *iface);
@@ -126,6 +130,9 @@ update_drive (GGduDrive *drive)
   gboolean old_is_media_removable;
   gboolean old_has_media;
   gboolean old_can_eject;
+  gboolean old_can_start;
+  gboolean old_can_stop;
+  gboolean old_start_stop_type;
   gboolean old_is_media_check_automatic;
   gboolean old_can_poll_for_media;
 
@@ -133,6 +140,9 @@ update_drive (GGduDrive *drive)
   old_is_media_removable = drive->is_media_removable;
   old_has_media = drive->has_media;
   old_can_eject = drive->can_eject;
+  old_can_start = drive->can_start;
+  old_can_stop = drive->can_stop;
+  old_start_stop_type = drive->start_stop_type;
   old_can_poll_for_media = drive->can_poll_for_media;
   old_is_media_check_automatic = drive->is_media_check_automatic;
 
@@ -161,6 +171,7 @@ update_drive (GGduDrive *drive)
       drive->is_media_removable = TRUE;
       drive->has_media = TRUE;
       drive->can_eject = FALSE;
+      drive->can_poll_for_media = FALSE;
     }
   else
     {
@@ -171,12 +182,60 @@ update_drive (GGduDrive *drive)
       /* All drives with removable media are ejectable
        *
        * See http://bugzilla.gnome.org/show_bug.cgi?id=576587 for why we want this.
+       *
+       * See also below where we e.g. set can_eject to TRUE for non-removable drives.
        */
-      drive->can_eject = gdu_device_drive_get_is_media_ejectable (device) || gdu_device_drive_get_requires_eject (device) || gdu_device_is_removable (device) || gdu_device_drive_get_can_detach (device);
+      drive->can_eject = gdu_device_drive_get_is_media_ejectable (device) || gdu_device_drive_get_requires_eject (device) || gdu_device_is_removable (device);
       drive->is_media_check_automatic = gdu_device_is_media_change_detected (device);
       drive->can_poll_for_media = TRUE;
     }
 
+  /* determine start/stop type */
+  drive->can_stop = FALSE;
+  drive->can_start = FALSE;
+  drive->start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN;
+  if (gdu_drive_is_activatable (GDU_DRIVE (drive->presentable)))
+    {
+      drive->can_stop  = gdu_drive_can_deactivate (GDU_DRIVE (drive->presentable));
+      drive->can_start = gdu_drive_can_activate (GDU_DRIVE (drive->presentable), NULL);
+      drive->start_stop_type = G_DRIVE_START_STOP_TYPE_MULTIDISK;
+    }
+  else if (device != NULL && gdu_device_drive_get_can_detach (device))
+    {
+      /* If the device is not ejectable, just detach on Eject() and claim to be ejectable.
+       *
+       * This is so we get the UI to display "Eject" instead of "Shutdown" since it is
+       * more familiar and the common case. The way this works is that after the Eject()
+       * method returns we call Detach() - see eject_cb() below.
+       *
+       * (Note that it's not enough to just call Detach() since some devices, such as
+       * the Kindle, only works with Eject(). So we call them both in order)
+       */
+      if (!gdu_device_drive_get_is_media_ejectable (device))
+        {
+          drive->can_eject = TRUE;
+          /* we still set this since
+           *
+           * a) it helps when debugging things using gvfs-mount(1) output
+           *    since the tool will print can_stop=0 but start_stop_type=shutdown
+           *
+           * b) we use it in eject_cb() to determine we need to call Detach()
+           *    after Eject() successfully completes
+           */
+          drive->start_stop_type = G_DRIVE_START_STOP_TYPE_SHUTDOWN;
+        }
+      else
+        {
+          /* So here the device is ejectable and detachable - for example, a USB CD-ROM
+           * drive or a CD-ROM drive in an Ultrabay - for these, we want to offer both
+           * "Eject" and "Shutdown" options in the UI
+           */
+          drive->can_stop = TRUE;
+          drive->can_start = FALSE;
+          drive->start_stop_type = G_DRIVE_START_STOP_TYPE_SHUTDOWN;
+        }
+    }
+
   if (device != NULL)
     g_object_unref (device);
 
@@ -193,6 +252,9 @@ update_drive (GGduDrive *drive)
   changed = !((old_is_media_removable == drive->is_media_removable) &&
               (old_has_media == drive->has_media) &&
               (old_can_eject == drive->can_eject) &&
+              (old_can_start == drive->can_start) &&
+              (old_can_stop == drive->can_stop) &&
+              (old_start_stop_type == drive->start_stop_type) &&
               (old_is_media_check_automatic == drive->is_media_check_automatic) &&
               (old_can_poll_for_media == drive->can_poll_for_media) &&
               (g_strcmp0 (old_name, drive->name) == 0) &&
@@ -363,94 +425,34 @@ g_gdu_drive_can_poll_for_media (GDrive *_drive)
   return drive->can_poll_for_media;
 }
 
-static void
-detach_cb (GduDevice *device,
-           GError    *error,
-           gpointer   user_data)
+static gboolean
+g_gdu_drive_can_start (GDrive *_drive)
 {
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-
-  if (error != NULL)
-    {
-      g_simple_async_result_set_from_error (simple, error);
-      g_error_free (error);
-    }
-
-  g_simple_async_result_complete (simple);
-  g_object_unref (simple);
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+  return drive->can_start;
 }
 
-static void
-eject_cb (GduDevice *device,
-          GError    *error,
-          gpointer   user_data)
+static gboolean
+g_gdu_drive_can_stop (GDrive *_drive)
 {
-  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
-
-  if (error != NULL)
-    {
-      g_simple_async_result_set_from_error (simple, error);
-      g_error_free (error);
-    }
-
-  g_simple_async_result_complete (simple);
-  g_object_unref (simple);
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+  return drive->can_stop;
 }
 
-
-static void
-g_gdu_drive_eject_do (GDrive              *_drive,
-                      GCancellable        *cancellable,
-                      GAsyncReadyCallback  callback,
-                      gpointer             user_data)
+static GDriveStartStopType
+g_gdu_drive_get_start_stop_type (GDrive *_drive)
 {
   GGduDrive *drive = G_GDU_DRIVE (_drive);
-  GSimpleAsyncResult *simple;
-  GduDevice *device;
+  return drive->start_stop_type;
+}
 
-  device = gdu_presentable_get_device (drive->presentable);
-  if (device == NULL)
-    {
-      simple = g_simple_async_result_new_error (G_OBJECT (drive),
-                                                callback,
-                                                user_data,
-                                                G_IO_ERROR,
-                                                G_IO_ERROR_FAILED,
-                                                "Drive is activatable and not running");
-      g_simple_async_result_complete_in_idle (simple);
-      g_object_unref (simple);
-    }
-  else
-    {
-      simple = g_simple_async_result_new (G_OBJECT (drive),
-                                          callback,
-                                          user_data,
-                                          NULL);
+/* ---------------------------------------------------------------------------------------------------- */
 
-      /* Note that we also offer the Eject option for non-removable
-       * devices (see update_drive() above) that are detachable so the
-       * device may actually not be removable when we get here...
-       *
-       * However, keep in mind that a device may be both removable and
-       * detachable (e.g. a usb optical drive)..
-       *
-       * Now, consider what would happen if we detached a USB optical
-       * drive? The device would power down without actually ejecting
-       * the media... and it would require a power-cycle or a replug
-       * to use it for other media. Therefore, never detach devices
-       * with removable media, only eject them.
-       */
-      if (gdu_device_drive_get_can_detach (device) && !gdu_device_is_removable (device))
-        {
-          gdu_device_op_drive_detach (device, detach_cb, simple);
-        }
-      else
-        {
-          gdu_device_op_drive_eject (device, eject_cb, simple);
-        }
-      g_object_unref (device);
-    }
-}
+typedef void (*UnmountsMountsFunc)  (GDrive              *drive,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data,
+                                     gpointer             on_all_unmounted_data);
 
 typedef struct {
   GDrive *drive;
@@ -460,6 +462,9 @@ typedef struct {
   GMountUnmountFlags flags;
 
   GList *pending_mounts;
+
+  UnmountsMountsFunc on_all_unmounted;
+  gpointer on_all_unmounted_data;
 } UnmountMountsOp;
 
 static void
@@ -475,12 +480,48 @@ free_unmount_mounts_op (UnmountMountsOp *data)
   g_list_free (data->pending_mounts);
 }
 
-static void _eject_unmount_mounts (UnmountMountsOp *data);
+static void
+unmount_mounts_cb (GObject       *source_object,
+                   GAsyncResult  *res,
+                   gpointer       user_data);
 
 static void
-_eject_unmount_mounts_cb (GObject *source_object,
-                          GAsyncResult *res,
-                          gpointer user_data)
+unmount_mounts_do (UnmountMountsOp *data)
+{
+  if (data->pending_mounts == NULL)
+    {
+
+      /*g_warning ("all pending mounts done");*/
+      data->on_all_unmounted (data->drive,
+                              data->cancellable,
+                              data->callback,
+                              data->user_data,
+                              data->on_all_unmounted_data);
+
+      g_object_unref (data->drive);
+      g_free (data);
+    }
+  else
+    {
+      GMount *mount;
+
+      mount = data->pending_mounts->data;
+      data->pending_mounts = g_list_remove (data->pending_mounts, mount);
+
+      /*g_warning ("unmounting %p", mount);*/
+
+      g_mount_unmount (mount,
+                       data->flags,
+                       data->cancellable,
+                       unmount_mounts_cb,
+                       data);
+    }
+}
+
+static void
+unmount_mounts_cb (GObject *source_object,
+                   GAsyncResult *res,
+                   gpointer user_data)
 {
   UnmountMountsOp *data = user_data;
   GMount *mount = G_MOUNT (source_object);
@@ -514,86 +555,471 @@ _eject_unmount_mounts_cb (GObject *source_object,
       /*g_warning ("successfully unmounted %p", mount);*/
 
       /* move on to the next mount.. */
-      _eject_unmount_mounts (data);
+      unmount_mounts_do (data);
     }
 
   g_object_unref (mount);
 }
 
 static void
-_eject_unmount_mounts (UnmountMountsOp *data)
+unmount_mounts (GGduDrive           *drive,
+                GMountUnmountFlags   flags,
+                GCancellable        *cancellable,
+                GAsyncReadyCallback  callback,
+                gpointer             user_data,
+                UnmountsMountsFunc   on_all_unmounted,
+                gpointer             on_all_unmounted_data)
 {
   GMount *mount;
+  UnmountMountsOp *data;
+  GList *l;
 
-  if (data->pending_mounts == NULL)
+  data = g_new0 (UnmountMountsOp, 1);
+  data->drive = g_object_ref (drive);
+  data->cancellable = cancellable;
+  data->callback = callback;
+  data->user_data = user_data;
+  data->flags = flags;
+  data->on_all_unmounted = on_all_unmounted;
+  data->on_all_unmounted_data = on_all_unmounted_data;
+
+  for (l = drive->volumes; l != NULL; l = l->next)
     {
+      GGduVolume *volume = l->data;
+      mount = g_volume_get_mount (G_VOLUME (volume));
+      if (mount != NULL && g_mount_can_unmount (mount))
+        data->pending_mounts = g_list_prepend (data->pending_mounts, g_object_ref (mount));
+    }
 
-      /*g_warning ("all pending mounts done; ejecting drive");*/
+  unmount_mounts_do (data);
+}
 
-      g_gdu_drive_eject_do (data->drive,
-                            data->cancellable,
-                            data->callback,
-                            data->user_data);
+/* ---------------------------------------------------------------------------------------------------- */
 
-      g_object_unref (data->drive);
-      g_free (data);
+static void
+detach_after_eject_cb (GduDevice *device,
+                       GError    *error,
+                       gpointer   user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+  /* Don't return an error here - this is because some devices, such as
+   * the Kindle, can do Eject() but not Detach() e.g. the STOP UNIT
+   * command or any other part of Detach() may fail.
+   */
+  if (error != NULL)
+    {
+      g_warning ("Detach() after Eject() failed with: %s", error->message);
+      g_error_free (error);
+    }
+
+  g_simple_async_result_complete (simple);
+  g_object_unref (simple);
+}
+
+static void
+eject_cb (GduDevice *device,
+          GError    *error,
+          gpointer   user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  GGduDrive *drive;
+
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_simple_async_result_complete (simple);
+      g_object_unref (simple);
+      g_error_free (error);
+      goto out;
+    }
+
+  drive = G_GDU_DRIVE (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
+  if (drive->can_stop == FALSE && drive->start_stop_type == G_DRIVE_START_STOP_TYPE_SHUTDOWN)
+    {
+      /* If device is not ejectable but it is detachable and we don't support stop(),
+       * then also run Detach() after Eject() - see update_drive() for details for why...
+       */
+      gdu_device_op_drive_detach (device, detach_after_eject_cb, simple);
     }
   else
     {
-      mount = data->pending_mounts->data;
-      data->pending_mounts = g_list_remove (data->pending_mounts, mount);
+      /* otherwise we are done */
+      g_simple_async_result_complete (simple);
+      g_object_unref (simple);
+    }
+  g_object_unref (drive);
 
-      /*g_warning ("unmounting %p", mount);*/
+ out:
+  ;
+}
 
-      g_mount_unmount (mount,
-                       data->flags,
-                       data->cancellable,
-                       _eject_unmount_mounts_cb,
-                       data);
+static void
+g_gdu_drive_eject_on_all_unmounted (GDrive              *_drive,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data,
+                                    gpointer             on_all_unmounted_data)
+{
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+  GSimpleAsyncResult *simple;
+  GduDevice *device;
+
+  device = gdu_presentable_get_device (drive->presentable);
+  if (device == NULL)
+    {
+      simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                                callback,
+                                                user_data,
+                                                G_IO_ERROR,
+                                                G_IO_ERROR_FAILED,
+                                                "Drive is activatable and not running");
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+  else
+    {
+      simple = g_simple_async_result_new (G_OBJECT (drive),
+                                          callback,
+                                          user_data,
+                                          NULL);
+
+      gdu_device_op_drive_eject (device, eject_cb, simple);
     }
 }
 
 static void
-g_gdu_drive_eject (GDrive              *drive,
+g_gdu_drive_eject (GDrive              *_drive,
                    GMountUnmountFlags   flags,
                    GCancellable        *cancellable,
                    GAsyncReadyCallback  callback,
                    gpointer             user_data)
 {
-  GGduDrive *gdu_drive = G_GDU_DRIVE (drive);
-  UnmountMountsOp *data;
-  GList *l;
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
 
   /* first we need to go through all the volumes and unmount their assoicated mounts (if any) */
+  unmount_mounts (drive,
+                  flags,
+                  cancellable,
+                  callback,
+                  user_data,
+                  g_gdu_drive_eject_on_all_unmounted,
+                  NULL);
+}
 
-  data = g_new0 (UnmountMountsOp, 1);
-  data->drive = g_object_ref (drive);
-  data->cancellable = cancellable;
-  data->callback = callback;
-  data->user_data = user_data;
-  data->flags = flags;
+static gboolean
+g_gdu_drive_eject_finish (GDrive        *drive,
+                          GAsyncResult  *result,
+                          GError       **error)
+{
+  return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
 
-  for (l = gdu_drive->volumes; l != NULL; l = l->next)
+static void
+stop_cb (GduDevice *device,
+         GError    *error,
+         gpointer   user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+  if (error != NULL)
     {
-      GGduVolume *volume = l->data;
-      GMount *mount;
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+    }
 
-      mount = g_volume_get_mount (G_VOLUME (volume));
-      if (mount != NULL && g_mount_can_unmount (mount))
-        data->pending_mounts = g_list_prepend (data->pending_mounts, g_object_ref (mount));
+  g_simple_async_result_complete (simple);
+  g_object_unref (simple);
+}
+
+static void
+drive_deactivate_cb (GduDrive  *drive,
+                     GError    *error,
+                     gpointer   user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
     }
 
-  _eject_unmount_mounts (data);
+  g_simple_async_result_complete (simple);
+  g_object_unref (simple);
+}
+
+static void
+g_gdu_drive_stop_on_all_unmounted (GDrive              *_drive,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data,
+                                   gpointer             on_all_unmounted_data)
+{
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+  GSimpleAsyncResult *simple;
+  GduDevice *device;
+
+  device = gdu_presentable_get_device (drive->presentable);
+  if (device == NULL)
+    {
+      simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                                callback,
+                                                user_data,
+                                                G_IO_ERROR,
+                                                G_IO_ERROR_FAILED,
+                                                "Drive is activatable and not running");
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+  else
+    {
+      simple = g_simple_async_result_new (G_OBJECT (drive),
+                                          callback,
+                                          user_data,
+                                          NULL);
+
+      switch (drive->start_stop_type)
+        {
+        case G_DRIVE_START_STOP_TYPE_SHUTDOWN:
+          gdu_device_op_drive_detach (device, stop_cb, simple);
+          break;
+
+        case G_DRIVE_START_STOP_TYPE_MULTIDISK:
+          gdu_drive_deactivate (GDU_DRIVE (drive->presentable), drive_deactivate_cb, simple);
+          break;
+
+        default:
+          g_simple_async_result_set_error (simple,
+                                           G_IO_ERROR,
+                                           G_IO_ERROR_NOT_SUPPORTED,
+                                           "start_stop_type %d not supported",
+                                           drive->start_stop_type);
+          g_simple_async_result_complete_in_idle (simple);
+          g_object_unref (simple);
+          break;
+        }
+    }
+}
+
+static void
+g_gdu_drive_stop (GDrive              *_drive,
+                  GMountUnmountFlags   flags,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+
+  /* first we need to go through all the volumes and unmount their assoicated mounts (if any) */
+  unmount_mounts (drive,
+                  flags,
+                  cancellable,
+                  callback,
+                  user_data,
+                  g_gdu_drive_stop_on_all_unmounted,
+                  NULL);
 }
 
 static gboolean
-g_gdu_drive_eject_finish (GDrive        *drive,
+g_gdu_drive_stop_finish (GDrive        *drive,
+                         GAsyncResult  *result,
+                         GError       **error)
+{
+  return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+start_cb (GduDrive   *drive,
+          gchar      *assembled_drive_object_path,
+          GError     *error,
+          gpointer    user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+
+  if (error != NULL)
+    {
+      g_simple_async_result_set_error (simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_FAILED,
+                                       "Failed activating drive: %s",
+                                       error->message);
+      g_error_free (error);
+    }
+  else
+    {
+      g_free (assembled_drive_object_path);
+    }
+  g_simple_async_result_complete (simple);
+}
+
+typedef struct
+{
+  GGduDrive *drive;
+  GSimpleAsyncResult *simple;
+
+  GMountOperation *start_operation;
+  gulong start_operation_reply_handler_id;
+} StartOpData;
+
+static void
+start_operation_reply (GMountOperation      *op,
+                       GMountOperationResult result,
+                       gpointer              user_data)
+{
+  StartOpData *data = user_data;
+  gint choice;
+
+  /* we got what we wanted; don't listen to any other signals from the start operation */
+  if (data->start_operation_reply_handler_id != 0)
+    {
+      g_signal_handler_disconnect (data->start_operation, data->start_operation_reply_handler_id);
+      data->start_operation_reply_handler_id = 0;
+    }
+
+  if (result != G_MOUNT_OPERATION_HANDLED)
+    {
+      if (result == G_MOUNT_OPERATION_ABORTED)
+        {
+          /* The user aborted the operation so consider it "handled" */
+          g_simple_async_result_set_error (data->simple,
+                                           G_IO_ERROR,
+                                           G_IO_ERROR_FAILED_HANDLED,
+                                           "Start operation dialog aborted (user should never see this error since "
+                                           "it is G_IO_ERROR_FAILED_HANDLED)");
+        }
+      else
+        {
+          g_simple_async_result_set_error (data->simple,
+                                           G_IO_ERROR,
+                                           G_IO_ERROR_FAILED,
+                                           "Expected G_MOUNT_OPERATION_HANDLED but got %d", result);
+        }
+      g_simple_async_result_complete (data->simple);
+      goto out;
+    }
+
+  /* handle the user pressing cancel */
+  choice = g_mount_operation_get_choice (data->start_operation);
+  if (choice == 1)
+    {
+      g_simple_async_result_set_error (data->simple,
+                                       G_IO_ERROR,
+                                       G_IO_ERROR_FAILED_HANDLED,
+                                       "User refused to start degraded array (user should never see this error since "
+                                       "it is G_IO_ERROR_FAILED_HANDLED)");
+      g_simple_async_result_complete (data->simple);
+      goto out;
+    }
+
+  gdu_drive_activate (GDU_DRIVE (data->drive->presentable), start_cb, data->simple);
+
+ out:
+  g_object_unref (data->drive);
+  g_object_unref (data->start_operation);
+  g_free (data);
+}
+
+static void
+g_gdu_drive_start (GDrive              *_drive,
+                   GDriveStartFlags     flags,
+                   GMountOperation     *start_operation,
+                   GCancellable        *cancellable,
+                   GAsyncReadyCallback  callback,
+                   gpointer             user_data)
+{
+  GGduDrive *drive = G_GDU_DRIVE (_drive);
+  GSimpleAsyncResult *simple;
+  gboolean degraded;
+
+  /* TODO: handle GCancellable */
+
+  if (!gdu_drive_can_activate (GDU_DRIVE (drive->presentable), &degraded))
+    goto not_supported;
+
+  if (start_operation == NULL && degraded)
+    goto refuse_degraded_without_confirmation;
+
+  simple = g_simple_async_result_new (G_OBJECT (drive),
+                                      callback,
+                                      user_data,
+                                      NULL);
+
+  if (degraded)
+    {
+      const gchar *message;
+      const gchar *choices[3];
+      StartOpData *data;
+
+      message = _("Start drive in degraded mode?\n"
+                  "Starting a drive in degraded mode means that "
+                  "the drive is no longer tolerant to failures. "
+                  "Data on the drive may be irrevocably lost if a "
+                  "component fails.");
+
+      choices[0] = _("Start Anyway");
+      choices[1] = _("Cancel");
+      choices[2] = NULL;
+
+      data = g_new0 (StartOpData, 1);
+      data->drive = g_object_ref (drive);
+      data->simple = simple;
+      data->start_operation = g_object_ref (start_operation);
+      data->start_operation_reply_handler_id = g_signal_connect (start_operation,
+                                                                 "reply",
+                                                                 G_CALLBACK (start_operation_reply),
+                                                                 data);
+
+      g_signal_emit_by_name (start_operation,
+                             "ask-question",
+                             message,
+                             choices);
+    }
+  else
+    {
+      gdu_drive_activate (GDU_DRIVE (drive->presentable), start_cb, simple);
+    }
+
+  return;
+
+ not_supported:
+  simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                            callback,
+                                            user_data,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_NOT_SUPPORTED,
+                                            "Starting drive with start_stop_type %d is not supported",
+                                            drive->start_stop_type);
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+  return;
+
+ refuse_degraded_without_confirmation:
+  simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                            callback,
+                                            user_data,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_FAILED,
+                                            "Refusing to start degraded multidisk drive without user confirmation");
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+
+}
+
+static gboolean
+g_gdu_drive_start_finish (GDrive        *drive,
                           GAsyncResult  *result,
                           GError       **error)
 {
   return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 poll_media_cb (GduDevice *device,
                GError    *error,
@@ -656,6 +1082,8 @@ g_gdu_drive_poll_for_media_finish (GDrive        *drive,
   return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 static char *
 g_gdu_drive_get_identifier (GDrive              *_drive,
                             const char          *kind)
@@ -688,6 +1116,8 @@ g_gdu_drive_enumerate_identifiers (GDrive *_drive)
   return (gchar **) g_ptr_array_free (p, FALSE);
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 g_gdu_drive_drive_iface_init (GDriveIface *iface)
 {
@@ -706,6 +1136,14 @@ g_gdu_drive_drive_iface_init (GDriveIface *iface)
   iface->poll_for_media_finish = g_gdu_drive_poll_for_media_finish;
   iface->get_identifier = g_gdu_drive_get_identifier;
   iface->enumerate_identifiers = g_gdu_drive_enumerate_identifiers;
+
+  iface->get_start_stop_type = g_gdu_drive_get_start_stop_type;
+  iface->can_start = g_gdu_drive_can_start;
+  iface->can_stop = g_gdu_drive_can_stop;
+  iface->start = g_gdu_drive_start;
+  iface->start_finish = g_gdu_drive_start_finish;
+  iface->stop = g_gdu_drive_stop;
+  iface->stop_finish = g_gdu_drive_stop_finish;
 }
 
 gboolean
diff --git a/monitor/gdu/ggduvolumemonitor.c b/monitor/gdu/ggduvolumemonitor.c
index 6da7393..ac90ba4 100644
--- a/monitor/gdu/ggduvolumemonitor.c
+++ b/monitor/gdu/ggduvolumemonitor.c
@@ -836,7 +836,7 @@ should_drive_be_ignored (GduPool *pool, GduDrive *d, GList *fstab_mount_points)
 {
   GduDevice *device;
   gboolean ignored;
-  gboolean has_volumes;
+  gboolean have_volumes;
   gboolean all_volumes_are_ignored;
   GList *enclosed;
   GList *l;
@@ -847,24 +847,26 @@ should_drive_be_ignored (GduPool *pool, GduDrive *d, GList *fstab_mount_points)
 
   device = gdu_presentable_get_device (GDU_PRESENTABLE (d));
 
-  /* If there is no GduDevice for a drive, then ignore it.
-   *
-   * Note that right now the only drives without a GduDevice are Linux
-   * MD arrays not yet activated. In the future we might want to
-   * display these so the user can start the array.
+  /* If there is no GduDevice for a drive, then ignore it unless
+   * we know how to start it. Right now this is only relevant
+   * for GduLinuxMdDrive but we can add other stuff to libgdu
+   * in the future and things will work here
    */
   if (device == NULL)
     {
-      ignored = TRUE;
-      goto out;
+      if (!gdu_drive_is_activatable (d))
+        {
+          ignored = TRUE;
+          goto out;
+        }
     }
 
-  if (gdu_device_get_presentation_hide (device)) {
+  if (device != NULL && gdu_device_get_presentation_hide (device)) {
     ignored = TRUE;
     goto out;
   }
 
-  has_volumes = FALSE;
+  have_volumes = FALSE;
   all_volumes_are_ignored = TRUE;
 
   /* never ignore a drive if it has volumes that we don't want to ignore */
@@ -878,7 +880,7 @@ should_drive_be_ignored (GduPool *pool, GduDrive *d, GList *fstab_mount_points)
         {
           GduVolume *volume = GDU_VOLUME (enclosed_presentable);
 
-          has_volumes = TRUE;
+          have_volumes = TRUE;
 
           if (!should_volume_be_ignored (pool, volume, fstab_mount_points))
             {
@@ -894,23 +896,26 @@ should_drive_be_ignored (GduPool *pool, GduDrive *d, GList *fstab_mount_points)
    *
    * b) the volumes of the drive are all ignored
    */
-  if (!has_volumes)
-    {
-      if (gdu_device_is_media_available (device))
-        ignored = TRUE;
-    }
-  else
+  if (device != NULL)
     {
-      if (all_volumes_are_ignored)
-        ignored = TRUE;
-    }
+      if (!have_volumes)
+        {
+          if (gdu_device_is_media_available (device))
+            ignored = TRUE;
+        }
+      else
+        {
+          if (all_volumes_are_ignored)
+            ignored = TRUE;
+        }
 
-  /* special case for audio discs: don't ignore the drive since we'll create
-   * a cdda:// mount for the drive
-   */
-  if (gdu_device_is_optical_disc (device) && gdu_device_optical_disc_get_num_audio_tracks (device) > 0)
-    {
-      ignored = FALSE;
+      /* special case for audio discs: don't ignore the drive since we'll create
+       * a cdda:// mount for the drive
+       */
+      if (gdu_device_is_optical_disc (device) && gdu_device_optical_disc_get_num_audio_tracks (device) > 0)
+        {
+          ignored = FALSE;
+        }
     }
 
  out:
diff --git a/monitor/proxy/gproxydrive.c b/monitor/proxy/gproxydrive.c
index 7945993..5f027b8 100644
--- a/monitor/proxy/gproxydrive.c
+++ b/monitor/proxy/gproxydrive.c
@@ -53,8 +53,13 @@ struct _GProxyDrive {
   gboolean is_media_check_automatic;
   gboolean has_media;
   gboolean is_media_removable;
+  gboolean can_start;
+  gboolean can_stop;
+  GDriveStartStopType start_stop_type;
 
   GHashTable *identifiers;
+
+  GHashTable *hash_start_op_id_to_data;
 };
 
 static void g_proxy_drive_drive_iface_init (GDriveIface *iface);
@@ -87,6 +92,8 @@ g_proxy_drive_finalize (GObject *object)
   if (drive->identifiers != NULL)
     g_hash_table_unref (drive->identifiers);
 
+  g_hash_table_unref (drive->hash_start_op_id_to_data);
+
   if (G_OBJECT_CLASS (g_proxy_drive_parent_class)->finalize)
     (*G_OBJECT_CLASS (g_proxy_drive_parent_class)->finalize) (object);
 }
@@ -107,6 +114,7 @@ g_proxy_drive_class_finalize (GProxyDriveClass *klass)
 static void
 g_proxy_drive_init (GProxyDrive *proxy_drive)
 {
+  proxy_drive->hash_start_op_id_to_data = g_hash_table_new (g_str_hash, g_str_equal);
 }
 
 GProxyDrive *
@@ -129,10 +137,13 @@ g_proxy_drive_new (GProxyVolumeMonitor *volume_monitor)
  * boolean              has-media
  * boolean              is-media-removable
  * boolean              is-media-check-automatic
+ * boolean              can-start
+ * boolean              can-stop
+ * uint32               start-stop-type
  * array:string         volume-ids
  * dict:string->string  identifiers
  */
-#define DRIVE_STRUCT_TYPE "(sssbbbbasa{ss})"
+#define DRIVE_STRUCT_TYPE "(sssbbbbbbuasa{ss})"
 
 void
 g_proxy_drive_update (GProxyDrive         *drive,
@@ -148,6 +159,9 @@ g_proxy_drive_update (GProxyDrive         *drive,
   dbus_bool_t has_media;
   dbus_bool_t is_media_removable;
   dbus_bool_t is_media_check_automatic;
+  dbus_bool_t can_start;
+  dbus_bool_t can_stop;
+  dbus_uint32_t start_stop_type;
   GPtrArray *volume_ids;
   GHashTable *identifiers;
 
@@ -168,6 +182,12 @@ g_proxy_drive_update (GProxyDrive         *drive,
   dbus_message_iter_next (&iter_struct);
   dbus_message_iter_get_basic (&iter_struct, &is_media_check_automatic);
   dbus_message_iter_next (&iter_struct);
+  dbus_message_iter_get_basic (&iter_struct, &can_start);
+  dbus_message_iter_next (&iter_struct);
+  dbus_message_iter_get_basic (&iter_struct, &can_stop);
+  dbus_message_iter_next (&iter_struct);
+  dbus_message_iter_get_basic (&iter_struct, &start_stop_type);
+  dbus_message_iter_next (&iter_struct);
 
   volume_ids = g_ptr_array_new ();
   dbus_message_iter_recurse (&iter_struct, &iter_volume_ids_iter);
@@ -215,6 +235,9 @@ g_proxy_drive_update (GProxyDrive         *drive,
   drive->has_media = has_media;
   drive->is_media_removable = is_media_removable;
   drive->is_media_check_automatic = is_media_check_automatic;
+  drive->can_start = can_start;
+  drive->can_stop = can_stop;
+  drive->start_stop_type = start_stop_type;
   drive->identifiers = identifiers != NULL ? g_hash_table_ref (identifiers) : NULL;
   drive->volume_ids = g_strdupv ((char **) volume_ids->pdata);
 
@@ -353,6 +376,45 @@ g_proxy_drive_can_poll_for_media (GDrive *drive)
   return res;
 }
 
+static gboolean
+g_proxy_drive_can_start (GDrive *drive)
+{
+  GProxyDrive *proxy_drive = G_PROXY_DRIVE (drive);
+  gboolean res;
+
+  G_LOCK (proxy_drive);
+  res = proxy_drive->can_start;
+  G_UNLOCK (proxy_drive);
+
+  return res;
+}
+
+static gboolean
+g_proxy_drive_can_stop (GDrive *drive)
+{
+  GProxyDrive *proxy_drive = G_PROXY_DRIVE (drive);
+  gboolean res;
+
+  G_LOCK (proxy_drive);
+  res = proxy_drive->can_stop;
+  G_UNLOCK (proxy_drive);
+
+  return res;
+}
+
+static GDriveStartStopType
+g_proxy_drive_get_start_stop_type (GDrive *drive)
+{
+  GProxyDrive *proxy_drive = G_PROXY_DRIVE (drive);
+  GDriveStartStopType res;
+
+  G_LOCK (proxy_drive);
+  res = proxy_drive->start_stop_type;
+  G_UNLOCK (proxy_drive);
+
+  return res;
+}
+
 static char *
 g_proxy_drive_get_identifier (GDrive              *drive,
                               const char          *kind)
@@ -466,6 +528,8 @@ operation_cancelled (GCancellable *cancellable,
   dbus_connection_unref (connection);
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 eject_cb (DBusMessage *reply,
           GError *error,
@@ -587,6 +651,557 @@ g_proxy_drive_eject_finish (GDrive        *drive,
   return TRUE;
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+stop_cb (DBusMessage *reply,
+          GError *error,
+          DBusOp *data)
+{
+  if (data->cancelled_handler_id > 0)
+    g_signal_handler_disconnect (data->cancellable, data->cancelled_handler_id);
+
+  if (!g_cancellable_is_cancelled (data->cancellable))
+    {
+      GSimpleAsyncResult *simple;
+
+      if (error != NULL)
+        simple = g_simple_async_result_new_from_error (G_OBJECT (data->drive),
+                                                       data->callback,
+                                                       data->user_data,
+                                                       error);
+      else
+        simple = g_simple_async_result_new (G_OBJECT (data->drive),
+                                            data->callback,
+                                            data->user_data,
+                                            NULL);
+      g_simple_async_result_complete (simple);
+      g_object_unref (simple);
+    }
+
+  g_object_unref (data->drive);
+  g_free (data->cancellation_id);
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+  g_free (data);
+}
+
+static void
+g_proxy_drive_stop (GDrive              *drive,
+                    GMountUnmountFlags   flags,
+                    GCancellable        *cancellable,
+                    GAsyncReadyCallback  callback,
+                    gpointer             user_data)
+{
+  GProxyDrive *proxy_drive = G_PROXY_DRIVE (drive);
+  DBusConnection *connection;
+  const char *name;
+  DBusMessage *message;
+  DBusOp *data;
+  dbus_uint32_t _flags = flags;
+
+  G_LOCK (proxy_drive);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      GSimpleAsyncResult *simple;
+      simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                                callback,
+                                                user_data,
+                                                G_IO_ERROR,
+                                                G_IO_ERROR_CANCELLED,
+                                                _("Operation was cancelled"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      G_UNLOCK (proxy_drive);
+      goto out;
+    }
+
+  data = g_new0 (DBusOp, 1);
+  data->drive = g_object_ref (drive);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  if (cancellable != NULL)
+    {
+      data->cancellation_id = g_strdup_printf ("%p", cancellable);
+      data->cancellable = g_object_ref (cancellable);
+      data->cancelled_handler_id = g_signal_connect (data->cancellable,
+                                                     "cancelled",
+                                                     G_CALLBACK (operation_cancelled),
+                                                     data);
+    }
+  else
+    {
+      data->cancellation_id = g_strdup ("");
+    }
+
+  connection = g_proxy_volume_monitor_get_dbus_connection (proxy_drive->volume_monitor);
+  name = g_proxy_volume_monitor_get_dbus_name (proxy_drive->volume_monitor);
+
+  message = dbus_message_new_method_call (name,
+                                          "/org/gtk/Private/RemoteVolumeMonitor",
+                                          "org.gtk.Private.RemoteVolumeMonitor",
+                                          "DriveStop");
+  dbus_message_append_args (message,
+                            DBUS_TYPE_STRING,
+                            &(proxy_drive->id),
+                            DBUS_TYPE_STRING,
+                            &(data->cancellation_id),
+                            DBUS_TYPE_UINT32,
+                            &_flags,
+                            DBUS_TYPE_INVALID);
+  G_UNLOCK (proxy_drive);
+
+  _g_dbus_connection_call_async (connection,
+                                 message,
+                                 -1,
+                                 (GAsyncDBusCallback) stop_cb,
+                                 data);
+  dbus_connection_unref (connection);
+  dbus_message_unref (message);
+ out:
+  ;
+}
+
+static gboolean
+g_proxy_drive_stop_finish (GDrive        *drive,
+                           GAsyncResult  *result,
+                           GError       **error)
+{
+  if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+    return FALSE;
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  GProxyDrive *drive;
+  GAsyncReadyCallback callback;
+  gpointer user_data;
+
+  gchar *cancellation_id;
+  GCancellable *cancellable;
+  gulong cancelled_handler_id;
+
+  gchar *start_op_id;
+  GMountOperation *start_operation;
+  gulong reply_handler_id;
+} DBusStartOp;
+
+static void
+start_cb (DBusMessage  *reply,
+          GError       *error,
+          DBusStartOp  *data)
+{
+  if (data->cancelled_handler_id > 0)
+    g_signal_handler_disconnect (data->cancellable, data->cancelled_handler_id);
+
+  if (!g_cancellable_is_cancelled (data->cancellable))
+    {
+      GSimpleAsyncResult *simple;
+
+      if (error != NULL)
+        simple = g_simple_async_result_new_from_error (G_OBJECT (data->drive),
+                                                       data->callback,
+                                                       data->user_data,
+                                                       error);
+      else
+        simple = g_simple_async_result_new (G_OBJECT (data->drive),
+                                            data->callback,
+                                            data->user_data,
+                                            NULL);
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+
+  /* free DBusStartOp */
+  if (strlen (data->start_op_id) > 0)
+    g_hash_table_remove (data->drive->hash_start_op_id_to_data, data->start_op_id);
+  g_object_unref (data->drive);
+
+  g_free (data->start_op_id);
+  if (data->reply_handler_id > 0)
+    g_signal_handler_disconnect (data->start_operation, data->reply_handler_id);
+  if (data->start_operation != NULL)
+    g_object_unref (data->start_operation);
+
+  g_free (data->cancellation_id);
+  if (data->cancellable != NULL)
+    g_object_unref (data->cancellable);
+
+  g_free (data);
+}
+
+static void
+start_cancelled (GCancellable *cancellable,
+                 gpointer      user_data)
+{
+  DBusStartOp *data = user_data;
+  GSimpleAsyncResult *simple;
+  DBusConnection *connection;
+  DBusMessage *message;
+  const char *name;
+
+  G_LOCK (proxy_drive);
+
+  simple = g_simple_async_result_new_error (G_OBJECT (data->drive),
+                                            data->callback,
+                                            data->user_data,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_CANCELLED,
+                                            _("Operation was cancelled"));
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+
+  /* Now tell the remote drive monitor that the op has been cancelled */
+  connection = g_proxy_volume_monitor_get_dbus_connection (data->drive->volume_monitor);
+  name = g_proxy_volume_monitor_get_dbus_name (data->drive->volume_monitor);
+  message = dbus_message_new_method_call (name,
+                                          "/org/gtk/Private/RemoteVolumeMonitor",
+                                          "org.gtk.Private.RemoteVolumeMonitor",
+                                          "CancelOperation");
+  dbus_message_append_args (message,
+                            DBUS_TYPE_STRING,
+                            &(data->cancellation_id),
+                            DBUS_TYPE_INVALID);
+
+  G_UNLOCK (proxy_drive);
+
+  _g_dbus_connection_call_async (connection,
+                                 message,
+                                 -1,
+                                 (GAsyncDBusCallback) cancel_operation_reply_cb,
+                                 NULL);
+  dbus_message_unref (message);
+  dbus_connection_unref (connection);
+}
+
+static void
+g_proxy_drive_start (GDrive              *drive,
+                     GDriveStartFlags     flags,
+                     GMountOperation     *start_operation,
+                     GCancellable        *cancellable,
+                     GAsyncReadyCallback  callback,
+                     gpointer             user_data)
+{
+  GProxyDrive *proxy_drive = G_PROXY_DRIVE (drive);
+  DBusStartOp *data;
+  DBusConnection *connection;
+  const char *name;
+  DBusMessage *message;
+
+  G_LOCK (proxy_drive);
+
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      GSimpleAsyncResult *simple;
+      simple = g_simple_async_result_new_error (G_OBJECT (drive),
+                                                callback,
+                                                user_data,
+                                                G_IO_ERROR,
+                                                G_IO_ERROR_CANCELLED,
+                                                _("Operation was cancelled"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      G_UNLOCK (proxy_drive);
+      goto out;
+    }
+
+  data = g_new0 (DBusStartOp, 1);
+  data->drive = g_object_ref (drive);
+  data->callback = callback;
+  data->user_data = user_data;
+  if (cancellable != NULL)
+    {
+      data->cancellation_id = g_strdup_printf ("%p", cancellable);
+      data->cancellable = g_object_ref (cancellable);
+      data->cancelled_handler_id = g_signal_connect (data->cancellable,
+                                                     "cancelled",
+                                                     G_CALLBACK (start_cancelled),
+                                                     data);
+    }
+  else
+    {
+      data->cancellation_id = g_strdup ("");
+    }
+
+  if (start_operation != NULL)
+    {
+      data->start_op_id = g_strdup_printf ("%p", start_operation);
+      data->start_operation = g_object_ref (start_operation);
+      g_hash_table_insert (proxy_drive->hash_start_op_id_to_data,
+                           data->start_op_id,
+                           data);
+    }
+  else
+    {
+      data->start_op_id = g_strdup ("");
+    }
+
+  connection = g_proxy_volume_monitor_get_dbus_connection (proxy_drive->volume_monitor);
+  name = g_proxy_volume_monitor_get_dbus_name (proxy_drive->volume_monitor);
+
+  message = dbus_message_new_method_call (name,
+                                          "/org/gtk/Private/RemoteVolumeMonitor",
+                                          "org.gtk.Private.RemoteVolumeMonitor",
+                                          "DriveStart");
+  dbus_message_append_args (message,
+                            DBUS_TYPE_STRING,
+                            &(proxy_drive->id),
+                            DBUS_TYPE_STRING,
+                            &(data->cancellation_id),
+                            DBUS_TYPE_UINT32,
+                            &(flags),
+                            DBUS_TYPE_STRING,
+                            &(data->start_op_id),
+                            DBUS_TYPE_INVALID);
+  G_UNLOCK (proxy_drive);
+
+  _g_dbus_connection_call_async (connection,
+                                 message,
+                                 G_PROXY_VOLUME_MONITOR_DBUS_TIMEOUT,
+                                 (GAsyncDBusCallback) start_cb,
+                                 data);
+  dbus_message_unref (message);
+  dbus_connection_unref (connection);
+
+ out:
+  ;
+}
+
+
+static void
+start_op_reply_cb (DBusMessage *reply,
+                   GError      *error,
+                   DBusStartOp      *data)
+{
+  if (error != NULL)
+    {
+      g_warning ("Error from StartOpReply(): %s", error->message);
+    }
+}
+
+static void
+start_operation_reply (GMountOperation        *start_operation,
+                       GMountOperationResult  result,
+                       gpointer               user_data)
+{
+  DBusStartOp *data = user_data;
+  DBusConnection *connection;
+  const char *name;
+  DBusMessage *message;
+  const char *user_name;
+  const char *domain;
+  const char *password;
+  char *encoded_password;
+  dbus_uint32_t password_save;
+  dbus_uint32_t choice;
+  dbus_bool_t anonymous;
+
+  connection = g_proxy_volume_monitor_get_dbus_connection (data->drive->volume_monitor);
+  name = g_proxy_volume_monitor_get_dbus_name (data->drive->volume_monitor);
+
+  user_name     = g_mount_operation_get_username (start_operation);
+  domain        = g_mount_operation_get_domain (start_operation);
+  password      = g_mount_operation_get_password (start_operation);
+  password_save = g_mount_operation_get_password_save (start_operation);
+  choice        = g_mount_operation_get_choice (start_operation);
+  anonymous     = g_mount_operation_get_anonymous (start_operation);
+
+  if (user_name == NULL)
+    user_name = "";
+  if (domain == NULL)
+    domain = "";
+  if (password == NULL)
+    password = "";
+
+  /* NOTE: this is not to add "security", it's merely to prevent accidental exposure
+   *       of passwords when running dbus-monitor
+   */
+  encoded_password = g_base64_encode ((const guchar *) password, (gsize) (strlen (password) + 1));
+
+  message = dbus_message_new_method_call (name,
+                                          "/org/gtk/Private/RemoteVolumeMonitor",
+                                          "org.gtk.Private.RemoteVolumeMonitor",
+                                          "StartOpReply");
+  dbus_message_append_args (message,
+                            DBUS_TYPE_STRING,
+                            &(data->drive->id),
+                            DBUS_TYPE_STRING,
+                            &(data->start_op_id),
+                            DBUS_TYPE_INT32,
+                            &result,
+                            DBUS_TYPE_STRING,
+                            &user_name,
+                            DBUS_TYPE_STRING,
+                            &domain,
+                            DBUS_TYPE_STRING,
+                            &encoded_password,
+                            DBUS_TYPE_INT32,
+                            &password_save,
+                            DBUS_TYPE_INT32,
+                            &choice,
+                            DBUS_TYPE_BOOLEAN,
+                            &anonymous,
+                            DBUS_TYPE_INVALID);
+
+  _g_dbus_connection_call_async (connection,
+                                 message,
+                                 -1,
+                                 (GAsyncDBusCallback) start_op_reply_cb,
+                                 data);
+
+  g_free (encoded_password);
+  dbus_message_unref (message);
+  dbus_connection_unref (connection);
+}
+
+void
+g_proxy_drive_handle_start_op_ask_password (GProxyDrive        *drive,
+                                            DBusMessageIter    *iter)
+{
+  const char *start_op_id;
+  const char *message;
+  const char *default_user;
+  const char *default_domain;
+  dbus_int32_t flags;
+  DBusStartOp *data;
+
+  dbus_message_iter_get_basic (iter, &start_op_id);
+  dbus_message_iter_next (iter);
+
+  dbus_message_iter_get_basic (iter, &message);
+  dbus_message_iter_next (iter);
+
+  dbus_message_iter_get_basic (iter, &default_user);
+  dbus_message_iter_next (iter);
+
+  dbus_message_iter_get_basic (iter, &default_domain);
+  dbus_message_iter_next (iter);
+
+  dbus_message_iter_get_basic (iter, &flags);
+  dbus_message_iter_next (iter);
+
+  data = g_hash_table_lookup (drive->hash_start_op_id_to_data, start_op_id);
+
+  /* since eavesdropping is enabled on the session bus we get this signal even if it
+   * is for another application; so silently ignore it if it's not for us
+   */
+  if (data == NULL)
+    goto out;
+
+  if (data->reply_handler_id == 0)
+    {
+      data->reply_handler_id = g_signal_connect (data->start_operation,
+                                                 "reply",
+                                                 G_CALLBACK (start_operation_reply),
+                                                 data);
+    }
+
+  g_signal_emit_by_name (data->start_operation,
+                         "ask-password",
+                         message,
+                         default_user,
+                         default_domain,
+                         flags);
+
+ out:
+  ;
+}
+
+void
+g_proxy_drive_handle_start_op_ask_question (GProxyDrive        *drive,
+                                            DBusMessageIter    *iter)
+{
+  const char *start_op_id;
+  const char *message;
+  GPtrArray *choices;
+  DBusMessageIter iter_array;
+  DBusStartOp *data;
+
+  choices = NULL;
+
+  dbus_message_iter_get_basic (iter, &start_op_id);
+  dbus_message_iter_next (iter);
+
+  dbus_message_iter_get_basic (iter, &message);
+  dbus_message_iter_next (iter);
+
+  choices = g_ptr_array_new ();
+  dbus_message_iter_recurse (iter, &iter_array);
+  while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
+    {
+      const char *choice;
+      dbus_message_iter_get_basic (&iter_array, &choice);
+      dbus_message_iter_next (&iter_array);
+
+      g_ptr_array_add (choices, g_strdup (choice));
+    }
+  g_ptr_array_add (choices, NULL);
+
+  data = g_hash_table_lookup (drive->hash_start_op_id_to_data, start_op_id);
+
+  /* since eavesdropping is enabled on the session bus we get this signal even if it
+   * is for another application; so silently ignore it if it's not for us
+   */
+  if (data == NULL)
+    goto out;
+
+  if (data->reply_handler_id == 0)
+    {
+      data->reply_handler_id = g_signal_connect (data->start_operation,
+                                                 "reply",
+                                                 G_CALLBACK (start_operation_reply),
+                                                 data);
+    }
+
+  g_signal_emit_by_name (data->start_operation,
+                         "ask-question",
+                         message,
+                         choices->pdata);
+
+ out:
+  g_ptr_array_free (choices, TRUE);
+}
+
+void
+g_proxy_drive_handle_start_op_aborted (GProxyDrive        *drive,
+                                       DBusMessageIter    *iter)
+{
+  const char *start_op_id;
+  DBusStartOp *data;
+
+  dbus_message_iter_get_basic (iter, &start_op_id);
+  dbus_message_iter_next (iter);
+
+  data = g_hash_table_lookup (drive->hash_start_op_id_to_data, start_op_id);
+
+  /* since eavesdropping is enabled on the session bus we get this signal even if it
+   * is for another application; so silently ignore it if it's not for us
+   */
+  if (data == NULL)
+    goto out;
+
+  g_signal_emit_by_name (data->start_operation, "aborted");
+
+ out:
+  ;
+}
+
+static gboolean
+g_proxy_drive_start_finish (GDrive        *drive,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+    return FALSE;
+  return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 poll_for_media_cb (DBusMessage *reply,
                    GError *error,
@@ -703,6 +1318,8 @@ g_proxy_drive_poll_for_media_finish (GDrive        *drive,
   return TRUE;
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 
 static void
 g_proxy_drive_drive_iface_init (GDriveIface *iface)
@@ -722,6 +1339,13 @@ g_proxy_drive_drive_iface_init (GDriveIface *iface)
   iface->poll_for_media_finish = g_proxy_drive_poll_for_media_finish;
   iface->get_identifier = g_proxy_drive_get_identifier;
   iface->enumerate_identifiers = g_proxy_drive_enumerate_identifiers;
+  iface->can_start = g_proxy_drive_can_start;
+  iface->start = g_proxy_drive_start;
+  iface->start_finish = g_proxy_drive_start_finish;
+  iface->can_stop = g_proxy_drive_can_stop;
+  iface->stop = g_proxy_drive_stop;
+  iface->stop_finish = g_proxy_drive_stop_finish;
+  iface->get_start_stop_type = g_proxy_drive_get_start_stop_type;
 }
 
 void
diff --git a/monitor/proxy/gproxydrive.h b/monitor/proxy/gproxydrive.h
index 7494286..ab26244 100644
--- a/monitor/proxy/gproxydrive.h
+++ b/monitor/proxy/gproxydrive.h
@@ -50,6 +50,15 @@ void          g_proxy_drive_update       (GProxyDrive         *drive,
                                           DBusMessageIter     *iter);
 const char   *g_proxy_drive_get_id       (GProxyDrive         *drive);
 
+void          g_proxy_drive_handle_start_op_ask_password (GProxyDrive        *drive,
+                                                          DBusMessageIter    *iter);
+
+void          g_proxy_drive_handle_start_op_ask_question (GProxyDrive        *drive,
+                                                          DBusMessageIter    *iter);
+
+void          g_proxy_drive_handle_start_op_aborted      (GProxyDrive        *drive,
+                                                          DBusMessageIter    *iter);
+
 G_END_DECLS
 
 #endif /* __G_PROXY_DRIVE_H__ */
diff --git a/monitor/proxy/gproxyvolume.c b/monitor/proxy/gproxyvolume.c
index fb3402a..30c717f 100644
--- a/monitor/proxy/gproxyvolume.c
+++ b/monitor/proxy/gproxyvolume.c
@@ -936,7 +936,7 @@ g_proxy_volume_mount (GVolume             *volume,
 
       _g_dbus_connection_call_async (connection,
                                      message,
-                                     30 * 60 * 1000,                /* 30 minute timeout */
+                                     G_PROXY_VOLUME_MONITOR_DBUS_TIMEOUT,                /* 30 minute timeout */
                                      (GAsyncDBusCallback) mount_cb,
                                      data);
       dbus_message_unref (message);
diff --git a/monitor/proxy/gproxyvolumemonitor.c b/monitor/proxy/gproxyvolumemonitor.c
index aa79460..3b5a25d 100644
--- a/monitor/proxy/gproxyvolumemonitor.c
+++ b/monitor/proxy/gproxyvolumemonitor.c
@@ -639,7 +639,11 @@ filter_function (DBusConnection *connection, DBusMessage *message, void *user_da
   else  if (dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveChanged") ||
             dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveConnected") ||
             dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveDisconnected") ||
-            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveEjectButton"))
+            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveEjectButton") ||
+            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveStopButton") ||
+            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "StartOpAskPassword") ||
+            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "StartOpAskQuestion") ||
+            dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "StartOpAborted"))
     {
 
       dbus_message_iter_init (message, &iter);
@@ -693,6 +697,33 @@ filter_function (DBusConnection *connection, DBusMessage *message, void *user_da
               signal_emit_in_idle (monitor, "drive-eject-button", drive);
             }
         }
+      else if (strcmp (member, "DriveStopButton") == 0)
+        {
+          drive = g_hash_table_lookup (monitor->drives, id);
+          if (drive != NULL)
+            {
+              signal_emit_in_idle (drive, "stop-button", NULL);
+              signal_emit_in_idle (monitor, "drive-stop-button", drive);
+            }
+        }
+      else if (strcmp (member, "StartOpAskPassword") == 0)
+        {
+          drive = g_hash_table_lookup (monitor->drives, id);
+          if (drive != NULL)
+            g_proxy_drive_handle_start_op_ask_password (drive, &iter);
+        }
+      else if (strcmp (member, "StartOpAskQuestion") == 0)
+        {
+          drive = g_hash_table_lookup (monitor->drives, id);
+          if (drive != NULL)
+            g_proxy_drive_handle_start_op_ask_question (drive, &iter);
+        }
+      else if (strcmp (member, "StartOpAborted") == 0)
+        {
+          drive = g_hash_table_lookup (monitor->drives, id);
+          if (drive != NULL)
+            g_proxy_drive_handle_start_op_aborted (drive, &iter);
+        }
 
     }
   else if (dbus_message_is_signal (message, "org.gtk.Private.RemoteVolumeMonitor", "VolumeChanged") ||
diff --git a/monitor/proxy/gproxyvolumemonitor.h b/monitor/proxy/gproxyvolumemonitor.h
index 96b9d84..3d29d7b 100644
--- a/monitor/proxy/gproxyvolumemonitor.h
+++ b/monitor/proxy/gproxyvolumemonitor.h
@@ -41,6 +41,14 @@ G_BEGIN_DECLS
 typedef struct _GProxyVolumeMonitor GProxyVolumeMonitor;
 typedef struct _GProxyVolumeMonitorClass GProxyVolumeMonitorClass;
 
+/* Timeout used for D-Bus messages in msec - this needs to be high enough
+ * to ensure that the user has time to interact with e.g. mount operation
+ * dialogs.
+ *
+ * We use 30 minutes.
+ */
+#define G_PROXY_VOLUME_MONITOR_DBUS_TIMEOUT 30*60*1000
+
 /* Forward definitions */
 typedef struct _GProxyDrive GProxyDrive;
 typedef struct _GProxyVolume GProxyVolume;
diff --git a/monitor/proxy/gvfsproxyvolumemonitordaemon.c b/monitor/proxy/gvfsproxyvolumemonitordaemon.c
index e59374d..d066672 100644
--- a/monitor/proxy/gvfsproxyvolumemonitordaemon.c
+++ b/monitor/proxy/gvfsproxyvolumemonitordaemon.c
@@ -205,10 +205,13 @@ static void monitor_try_create (void);
  * boolean              has-media
  * boolean              is-media-removable
  * boolean              is-media-check-automatic
+ * boolean              can-start
+ * boolean              can-stop
+ * uint32               start-stop-type
  * array:string         volume-ids
  * dict:string->string  identifiers
  */
-#define DRIVE_STRUCT_TYPE "(sssbbbbbasa{ss})"
+#define DRIVE_STRUCT_TYPE "(sssbbbbbbbuasa{ss})"
 
 static void
 append_drive (GDrive *drive, DBusMessageIter *iter_array)
@@ -225,6 +228,9 @@ append_drive (GDrive *drive, DBusMessageIter *iter_array)
   gboolean has_media;
   gboolean is_media_removable;
   gboolean is_media_check_automatic;
+  gboolean can_start;
+  gboolean can_stop;
+  GDriveStartStopType start_stop_type;
   GList *volumes, *l;
   char **identifiers;
   int n;
@@ -243,6 +249,9 @@ append_drive (GDrive *drive, DBusMessageIter *iter_array)
   has_media = g_drive_has_media (drive);
   is_media_removable = g_drive_is_media_removable (drive);
   is_media_check_automatic = g_drive_is_media_check_automatic (drive);
+  can_start = g_drive_can_start (drive);
+  can_stop = g_drive_can_stop (drive);
+  start_stop_type = g_drive_get_start_stop_type (drive);
   volumes = g_drive_get_volumes (drive);
   identifiers = g_drive_enumerate_identifiers (drive);
 
@@ -257,6 +266,9 @@ append_drive (GDrive *drive, DBusMessageIter *iter_array)
   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &has_media);
   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &is_media_removable);
   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &is_media_check_automatic);
+  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &can_start);
+  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_BOOLEAN, &can_stop);
+  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_UINT32, &start_stop_type);
 
   dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "s", &iter_volume_array);
   for (l = volumes; l != NULL; l = l->next)
@@ -1192,6 +1204,530 @@ handle_drive_eject (DBusConnection *connection, DBusMessage *message)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static DBusHandlerResult
+handle_start_op_reply (DBusConnection *connection, DBusMessage *message)
+{
+  const char *id;
+  const char *start_op_id;
+  dbus_int32_t result;
+  const char *user_name;
+  const char *domain;
+  const char *encoded_password;
+  char *decoded_password;
+  gsize decoded_password_len;
+  dbus_int32_t password_save;
+  dbus_int32_t choice;
+  dbus_bool_t anonymous;
+  DBusError dbus_error;
+  DBusHandlerResult ret;
+  GList *drives, *l;
+  GDrive *drive;
+  DBusMessage *reply;
+  GMountOperation *start_operation;
+
+  drives = NULL;
+  decoded_password = NULL;
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  dbus_error_init (&dbus_error);
+  if (!dbus_message_get_args (message, &dbus_error,
+                              DBUS_TYPE_STRING, &id,
+                              DBUS_TYPE_STRING, &start_op_id,
+                              DBUS_TYPE_INT32, &result,
+                              DBUS_TYPE_STRING, &user_name,
+                              DBUS_TYPE_STRING, &domain,
+                              DBUS_TYPE_STRING, &encoded_password,
+                              DBUS_TYPE_INT32, &password_save,
+                              DBUS_TYPE_INT32, &choice,
+                              DBUS_TYPE_BOOLEAN, &anonymous,
+                              DBUS_TYPE_INVALID))
+    {
+      g_warning ("Error parsing args for StartOpReply(): %s: %s", dbus_error.name, dbus_error.message);
+      dbus_error_free (&dbus_error);
+      goto out;
+    }
+
+  print_debug ("in handle_start_op_reply");
+
+  ret = DBUS_HANDLER_RESULT_HANDLED;
+
+  drive = NULL;
+  drives = g_volume_monitor_get_connected_drives (monitor);
+  for (l = drives; l != NULL; l = l->next)
+    {
+      char *drive_id;
+
+      drive = G_DRIVE (l->data);
+      drive_id = g_strdup_printf ("%p", drive);
+      if (strcmp (drive_id, id) == 0)
+        break;
+
+      g_free (drive_id);
+    }
+  if (l == NULL)
+    drive = NULL;
+
+  if (drive == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.NotFound",
+                                      "The given drive was not found");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  start_operation = g_object_get_data (G_OBJECT (drive), "start_operation");
+  if (start_operation == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.NotFound",
+                                      "No outstanding mount operation");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  decoded_password = (gchar *) g_base64_decode (encoded_password, &decoded_password_len);
+
+  g_mount_operation_set_username (start_operation, user_name);
+  g_mount_operation_set_domain (start_operation, domain);
+  g_mount_operation_set_password (start_operation, decoded_password);
+  g_mount_operation_set_password_save (start_operation, password_save);
+  g_mount_operation_set_choice (start_operation, choice);
+  g_mount_operation_set_anonymous (start_operation, anonymous);
+
+  g_mount_operation_reply (start_operation, result);
+
+  reply = dbus_message_new_method_return (message);
+  dbus_connection_send (connection, reply, NULL);
+  dbus_message_unref (reply);
+
+ out:
+  g_free (decoded_password);
+  if (drives != NULL)
+    {
+      g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+      g_list_free (drives);
+    }
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+drive_stop_cb (GDrive *drive, GAsyncResult *result, DBusMessage *message)
+{
+  GError *error;
+  DBusMessage *reply;
+
+  print_debug ("in drive_stop_cb");
+
+  g_object_set_data (G_OBJECT (drive), "cancellable", NULL);
+
+  error = NULL;
+  if (!g_drive_stop_finish (drive, result, &error))
+    {
+      print_debug ("  error: %s", error->message);
+      reply = _dbus_message_new_from_gerror (message, error);
+      g_error_free (error);
+    }
+  else
+    {
+      print_debug (" success");
+      reply = dbus_message_new_method_return (message);
+    }
+
+  dbus_connection_send (connection, reply, NULL);
+  dbus_message_unref (message);
+  dbus_message_unref (reply);
+}
+
+static DBusHandlerResult
+handle_drive_stop (DBusConnection *connection, DBusMessage *message)
+{
+  const char *id;
+  const char *cancellation_id;
+  const char *sender;
+  GCancellable *cancellable;
+  dbus_uint32_t unmount_flags;
+  DBusError dbus_error;
+  GList *drives, *l;
+  GDrive *drive;
+  DBusHandlerResult ret;
+
+  drive = NULL;
+  drives = NULL;
+  unmount_flags = 0;
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  dbus_error_init (&dbus_error);
+  if (!dbus_message_get_args (message, &dbus_error,
+                              DBUS_TYPE_STRING, &id,
+                              DBUS_TYPE_STRING, &cancellation_id,
+                              DBUS_TYPE_UINT32 &unmount_flags,
+                              DBUS_TYPE_INVALID))
+    {
+      g_warning ("Error parsing args for DriveStop(): %s: %s", dbus_error.name, dbus_error.message);
+      dbus_error_free (&dbus_error);
+      goto out;
+    }
+
+  print_debug ("in handle_drive_stop");
+
+  ret = DBUS_HANDLER_RESULT_HANDLED;
+
+  sender = dbus_message_get_sender (message);
+
+  drive = NULL;
+  drives = g_volume_monitor_get_connected_drives (monitor);
+  for (l = drives; l != NULL; l = l->next)
+    {
+      char *drive_id;
+
+      drive = G_DRIVE (l->data);
+      drive_id = g_strdup_printf ("%p", drive);
+      if (strcmp (drive_id, id) == 0)
+        break;
+
+      g_free (drive_id);
+    }
+  if (l == NULL)
+    drive = NULL;
+
+  if (drive == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.NotFound",
+                                      "The given drive was not found");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  if (g_object_get_data (G_OBJECT (drive), "cancellable") != NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.Failed",
+                                      "An operation is already pending");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  cancellable = g_cancellable_new ();
+  g_object_set_data_full (G_OBJECT (drive), "cancellable", cancellable, g_object_unref);
+  g_object_set_data_full (G_OBJECT (cancellable), "owner", g_strdup (sender), g_free);
+  g_object_set_data_full (G_OBJECT (cancellable), "cancellation_id", g_strdup (cancellation_id), g_free);
+  outstanding_ops = g_list_prepend (outstanding_ops, cancellable);
+  g_object_weak_ref (G_OBJECT (cancellable),
+                     cancellable_destroyed_cb,
+                     NULL);
+
+  g_drive_stop (drive,
+                unmount_flags,
+                cancellable,
+                (GAsyncReadyCallback) drive_stop_cb,
+                dbus_message_ref (message));
+
+ out:
+  if (drives != NULL)
+    {
+      g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+      g_list_free (drives);
+    }
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+drive_start_cb (GDrive *drive, GAsyncResult *result, DBusMessage *message)
+{
+  GError *error;
+  DBusMessage *reply;
+
+  print_debug ("in drive_start_cb");
+
+  g_object_set_data (G_OBJECT (drive), "start_operation", NULL);
+  g_object_set_data (G_OBJECT (drive), "cancellable", NULL);
+
+  error = NULL;
+  if (!g_drive_start_finish (drive, result, &error))
+    {
+      print_debug ("  error: %s", error->message);
+      reply = _dbus_message_new_from_gerror (message, error);
+      g_error_free (error);
+    }
+  else
+    {
+      print_debug (" success");
+      reply = dbus_message_new_method_return (message);
+    }
+
+  dbus_connection_send (connection, reply, NULL);
+  dbus_message_unref (message);
+  dbus_message_unref (reply);
+}
+
+static void
+start_ask_password_cb (GMountOperation  *start_operation,
+                       const gchar      *message_to_show,
+                       const gchar      *default_user,
+                       const gchar      *default_domain,
+                       GAskPasswordFlags flags,
+                       gpointer          user_data)
+{
+  gchar *id;
+  DBusMessage *message;
+  DBusMessageIter iter;
+  GDrive *drive;
+  const gchar *start_op_id;
+  const gchar *start_op_owner;
+
+  print_debug ("in ask_password_cb %s", message_to_show);
+
+  drive = G_DRIVE (user_data);
+
+  id = g_strdup_printf ("%p", drive);
+
+  start_op_id = g_object_get_data (G_OBJECT (start_operation), "start_op_id");
+  start_op_owner = g_object_get_data (G_OBJECT (start_operation), "start_op_owner");
+
+  message = dbus_message_new_signal ("/org/gtk/Private/RemoteVolumeMonitor",
+                                     "org.gtk.Private.RemoteVolumeMonitor",
+                                     "StartOpAskPassword");
+  dbus_message_iter_init_append (message, &iter);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &the_dbus_name);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &id);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &start_op_id);
+
+  if (message_to_show == NULL)
+    message_to_show = "";
+
+  if (default_user == NULL)
+    default_user = "";
+
+  if (default_domain == NULL)
+    default_domain = "";
+
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &message_to_show);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &default_user);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &default_domain);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_INT32, &flags);
+
+  dbus_message_set_destination (message, start_op_owner);
+
+  dbus_connection_send (connection, message, NULL);
+  dbus_message_unref (message);
+
+  g_free (id);
+}
+
+static void
+start_ask_question_cb (GMountOperation  *start_operation,
+                       const gchar      *message_to_show,
+                       gchar           **choices,
+                       gpointer          user_data)
+{
+  gchar *id;
+  DBusMessage *message;
+  DBusMessageIter iter;
+  DBusMessageIter iter_string_array;
+  const gchar *start_op_id;
+  const gchar *start_op_owner;
+  GDrive *drive;
+  guint n;
+
+  print_debug ("in ask_question_cb %s", message_to_show);
+
+  drive = G_DRIVE (user_data);
+
+  id = g_strdup_printf ("%p", drive);
+
+  start_op_id = g_object_get_data (G_OBJECT (start_operation), "start_op_id");
+  start_op_owner = g_object_get_data (G_OBJECT (start_operation), "start_op_owner");
+
+  message = dbus_message_new_signal ("/org/gtk/Private/RemoteVolumeMonitor",
+                                     "org.gtk.Private.RemoteVolumeMonitor",
+                                     "StartOpAskQuestion");
+  dbus_message_iter_init_append (message, &iter);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &the_dbus_name);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &id);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &start_op_id);
+
+  if (message_to_show == NULL)
+    message_to_show = "";
+
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &message_to_show);
+
+  dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &iter_string_array);
+  for (n = 0; choices != NULL && choices[n] != NULL; n++)
+    dbus_message_iter_append_basic (&iter_string_array, DBUS_TYPE_STRING, &(choices[n]));
+  dbus_message_iter_close_container (&iter, &iter_string_array);
+
+  dbus_message_set_destination (message, start_op_owner);
+
+  dbus_connection_send (connection, message, NULL);
+  dbus_message_unref (message);
+
+  g_free (id);
+}
+
+static void
+start_aborted_cb (GMountOperation  *start_operation,
+                  gpointer          user_data)
+{
+  gchar *id;
+  DBusMessage *message;
+  DBusMessageIter iter;
+  GDrive *drive;
+  const gchar *start_op_id;
+  const gchar *start_op_owner;
+
+  print_debug ("in aborted_cb");
+
+  drive = G_DRIVE (user_data);
+
+  id = g_strdup_printf ("%p", drive);
+
+  start_op_id = g_object_get_data (G_OBJECT (start_operation), "start_op_id");
+  start_op_owner = g_object_get_data (G_OBJECT (start_operation), "start_op_owner");
+
+  message = dbus_message_new_signal ("/org/gtk/Private/RemoteVolumeMonitor",
+                                     "org.gtk.Private.RemoteVolumeMonitor",
+                                     "StartOpAborted");
+  dbus_message_iter_init_append (message, &iter);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &the_dbus_name);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &id);
+  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &start_op_id);
+
+  dbus_message_set_destination (message, start_op_owner);
+
+  dbus_connection_send (connection, message, NULL);
+  dbus_message_unref (message);
+
+  g_free (id);
+}
+
+static DBusHandlerResult
+handle_drive_start (DBusConnection *connection, DBusMessage *message)
+{
+  const char *id;
+  const char *cancellation_id;
+  const char *sender;
+  const char *start_op_id;
+  GDriveStartFlags flags;
+  DBusError dbus_error;
+  GList *drives, *l;
+  GDrive *drive;
+  DBusHandlerResult ret;
+  GMountOperation *start_operation;
+  GCancellable *cancellable;
+
+  drives = NULL;
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  dbus_error_init (&dbus_error);
+  if (!dbus_message_get_args (message, &dbus_error,
+                              DBUS_TYPE_STRING, &id,
+                              DBUS_TYPE_STRING, &cancellation_id,
+                              DBUS_TYPE_UINT32, &flags,
+                              DBUS_TYPE_STRING, &start_op_id,
+                              DBUS_TYPE_INVALID))
+    {
+      g_warning ("Error parsing args for DriveStart(): %s: %s", dbus_error.name, dbus_error.message);
+      dbus_error_free (&dbus_error);
+      goto out;
+    }
+
+  print_debug ("in handle_drive_start");
+
+  ret = DBUS_HANDLER_RESULT_HANDLED;
+
+  sender = dbus_message_get_sender (message);
+
+  drive = NULL;
+  drives = g_volume_monitor_get_connected_drives (monitor);
+  for (l = drives; l != NULL; l = l->next)
+    {
+      char *drive_id;
+
+      drive = G_DRIVE (l->data);
+      drive_id = g_strdup_printf ("%p", drive);
+      if (strcmp (drive_id, id) == 0)
+        break;
+
+      g_free (drive_id);
+    }
+  if (l == NULL)
+    drive = NULL;
+
+  if (drive == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.NotFound",
+                                      "The given drive was not found");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  if (g_object_get_data (G_OBJECT (drive), "cancellable") != NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.gtk.Private.RemoteVolumeMonitor.Failed",
+                                      "An operation is already pending");
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+      goto out;
+    }
+
+  start_operation = NULL;
+  if (start_op_id != NULL && strlen (start_op_id) > 0)
+    {
+      start_operation = g_proxy_mount_operation_new ();
+      g_signal_connect (start_operation, "ask-password", G_CALLBACK (start_ask_password_cb), drive);
+      g_signal_connect (start_operation, "ask-question", G_CALLBACK (start_ask_question_cb), drive);
+      g_signal_connect (start_operation, "aborted", G_CALLBACK (start_aborted_cb), drive);
+      g_object_set_data_full (G_OBJECT (start_operation), "start_op_id", g_strdup (start_op_id), g_free);
+      g_object_set_data_full (G_OBJECT (start_operation), "start_op_owner", g_strdup (sender), g_free);
+      g_object_set_data_full (G_OBJECT (drive), "start_operation", start_operation, g_object_unref);
+    }
+
+  cancellable = g_cancellable_new ();
+  g_object_set_data_full (G_OBJECT (drive), "cancellable", cancellable, g_object_unref);
+  g_object_set_data_full (G_OBJECT (cancellable), "owner", g_strdup (sender), g_free);
+  g_object_set_data_full (G_OBJECT (cancellable), "cancellation_id", g_strdup (cancellation_id), g_free);
+  outstanding_ops = g_list_prepend (outstanding_ops, cancellable);
+  g_object_weak_ref (G_OBJECT (cancellable),
+                     cancellable_destroyed_cb,
+                     NULL);
+
+  g_drive_start (drive,
+                 flags,
+                 start_operation,
+                 cancellable,
+                 (GAsyncReadyCallback) drive_start_cb,
+                 dbus_message_ref (message));
+
+ out:
+  if (drives != NULL)
+    {
+      g_list_foreach (drives, (GFunc) g_object_unref, NULL);
+      g_list_free (drives);
+    }
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 drive_poll_for_media_cb (GDrive *drive, GAsyncResult *result, DBusMessage *message)
 {
@@ -1493,6 +2029,15 @@ filter_function (DBusConnection *connection, DBusMessage *message, void *user_da
               else if (dbus_message_is_method_call (message, "org.gtk.Private.RemoteVolumeMonitor", "DrivePollForMedia"))
                 ret = handle_drive_poll_for_media (connection, message);
 
+              else if (dbus_message_is_method_call (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveStart"))
+                ret = handle_drive_start (connection, message);
+
+              else if (dbus_message_is_method_call (message, "org.gtk.Private.RemoteVolumeMonitor", "DriveStop"))
+                ret = handle_drive_stop (connection, message);
+
+              else if (dbus_message_is_method_call (message, "org.gtk.Private.RemoteVolumeMonitor", "StartOpReply"))
+                ret = handle_start_op_reply (connection, message);
+
             }
         }
     }
@@ -1548,11 +2093,16 @@ drive_disconnected (GVolumeMonitor *monitor, GDrive *drive, DBusConnection *conn
 static void
 drive_eject_button (GVolumeMonitor *monitor, GDrive *drive, DBusConnection *connection)
 {
-  g_warning ("drive eject button!");
   emit_signal (connection, "DriveEjectButton", drive, (AppendFunc) append_drive);
 }
 
 static void
+drive_stop_button (GVolumeMonitor *monitor, GDrive *drive, DBusConnection *connection)
+{
+  emit_signal (connection, "DriveStopButton", drive, (AppendFunc) append_drive);
+}
+
+static void
 volume_changed (GVolumeMonitor *monitor, GVolume *volume, DBusConnection *connection)
 {
   emit_signal (connection, "VolumeChanged", volume, (AppendFunc) append_volume);
@@ -1734,6 +2284,7 @@ g_vfs_proxy_volume_monitor_daemon_main (int argc,
       g_signal_connect (monitor, "drive-connected", (GCallback) drive_connected, connection);
       g_signal_connect (monitor, "drive-disconnected", (GCallback) drive_disconnected, connection);
       g_signal_connect (monitor, "drive-eject-button", (GCallback) drive_eject_button, connection);
+      g_signal_connect (monitor, "drive-stop-button", (GCallback) drive_stop_button, connection);
 
       g_signal_connect (monitor, "volume-changed", (GCallback) volume_changed, connection);
       g_signal_connect (monitor, "volume-added", (GCallback) volume_added, connection);
diff --git a/programs/gvfs-mount.c b/programs/gvfs-mount.c
index b72b31d..b2699a6 100644
--- a/programs/gvfs-mount.c
+++ b/programs/gvfs-mount.c
@@ -542,6 +542,9 @@ list_drives (GList *drives,
       
       if (extra_detail)
 	{
+          GEnumValue *enum_value;
+          gpointer klass;
+
 	  ids = g_drive_enumerate_identifiers (drive);
 	  if (ids && ids[0] != NULL)
 	    {
@@ -569,8 +572,19 @@ list_drives (GList *drives,
 	  g_print ("%*sis_media_check_automatic=%d\n", indent + 2, "", g_drive_is_media_check_automatic (drive));
 	  g_print ("%*scan_poll_for_media=%d\n", indent + 2, "", g_drive_can_poll_for_media (drive));
 	  g_print ("%*scan_eject=%d\n", indent + 2, "", g_drive_can_eject (drive));
+	  g_print ("%*scan_start=%d\n", indent + 2, "", g_drive_can_start (drive));
+	  g_print ("%*scan_stop=%d\n", indent + 2, "", g_drive_can_stop (drive));
+
+          enum_value = NULL;
+          klass = g_type_class_ref (G_TYPE_DRIVE_START_STOP_TYPE);
+          if (klass != NULL)
+            {
+              enum_value = g_enum_get_value (klass, g_drive_get_start_stop_type (drive));
+              g_print ("%*sstart_stop_type=%s\n", indent + 2, "",
+                       enum_value != NULL ? enum_value->value_nick : "UNKNOWN");
+              g_type_class_unref (klass);
+            }
 	}
-      
       volumes = g_drive_get_volumes (drive);
       list_volumes (volumes, indent + 2, FALSE);
       g_list_foreach (volumes, (GFunc)g_object_unref, NULL);



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