[gnome-shell/hotplug: 7/14] mount-operation: add a ShellMountOperation implementation



commit b89d8b15e11d60a0257137fa23172282fabb264a
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Wed Jun 22 16:43:16 2011 -0400

    mount-operation: add a ShellMountOperation implementation
    
    Ideally, this would be an entirely-JS implementation, but we have a
    couple of issues with gjs and gobject-introspection to work around, so
    we need a ShellMountOperation class for the time being.
    
    This first commit implements the show-processes dialog, with a system
    modal style very similar to the EndSession dialog.
    Implementations of ask-question and ask-password will follow shortly.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=653520

 data/theme/gnome-shell.css   |   75 +++++++++++++
 js/Makefile.am               |    1 +
 js/ui/autorunManager.js      |   12 ++-
 js/ui/shellMountOperation.js |  249 ++++++++++++++++++++++++++++++++++++++++++
 src/Makefile.am              |    2 +
 src/shell-mount-operation.c  |  183 +++++++++++++++++++++++++++++++
 src/shell-mount-operation.h  |   63 +++++++++++
 7 files changed, 581 insertions(+), 4 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 79aca3b..cd18897 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1665,6 +1665,81 @@ StTooltip StLabel {
     color: #444444;
 }
 
+/* Show Processes Dialog */
+.shell-mount-operation-icon {
+    icon-size: 48px;
+}
+
+.show-processes-dialog {
+    spacing: 24px;
+}
+
+.show-processes-dialog-subject {
+    font-size: 12pt;
+    font-weight: bold;
+    color: #666666;
+    padding-top: 10px;
+    padding-left: 17px;
+    padding-bottom: 6px;
+}
+
+.show-processes-dialog-subject:rtl {
+    padding-left: 0px;
+    padding-right: 17px;
+}
+
+.show-processes-dialog-description {
+    font-size: 10pt;
+    color: white;
+    padding-left: 17px;
+    width: 28em;
+}
+
+.show-processes-dialog-description:rtl {
+    padding-right: 17px;
+}
+
+.show-processes-dialog-app-list {
+    font-size: 10pt;
+    max-height: 200px;
+    padding-top: 24px;
+    padding-left: 49px;
+    padding-right: 32px;
+}
+
+.show-processes-dialog-app-list:rtl {
+    padding-right: 49px;
+    padding-left: 32px;
+}
+
+.show-processes-dialog-app-list-item {
+    color: #ccc;
+}
+
+.show-processes-dialog-app-list-item:hover {
+    color: white;
+}
+
+.show-processes-dialog-app-list-item:ltr {
+    padding-right: 1em;
+}
+
+.show-processes-dialog-app-list-item:rtl {
+    padding-left: 1em;
+}
+
+.show-processes-dialog-app-list-item-icon:ltr {
+    padding-right: 17px;
+}
+
+.show-processes-dialog-app-list-item-icon:rtl {
+    padding-left: 17px;
+}
+
+.show-processes-dialog-app-list-item-name {
+    font-size: 10pt;
+}
+
 /* PolicyKit Authentication Dialog */
 .polkit-dialog {
     /* this is the width of the entire modal popup */
diff --git a/js/Makefile.am b/js/Makefile.am
index d8917a4..a54bffe 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -38,6 +38,7 @@ nobase_dist_js_DATA = 	\
 	ui/main.js		\
 	ui/messageTray.js	\
 	ui/modalDialog.js	\
+	ui/shellMountOperation.js \
 	ui/notificationDaemon.js \
 	ui/overview.js		\
 	ui/panel.js		\
diff --git a/js/ui/autorunManager.js b/js/ui/autorunManager.js
index 224a14d..f1ecf5d 100644
--- a/js/ui/autorunManager.js
+++ b/js/ui/autorunManager.js
@@ -7,6 +7,7 @@ const St = imports.gi.St;
 
 const Main = imports.ui.main;
 const MessageTray = imports.ui.messageTray;
+const ShellMountOperation = imports.ui.shellMountOperation;
 
 // GSettings keys
 const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
@@ -201,13 +202,13 @@ AutorunManager.prototype = {
     },
 
     ejectMount: function(mount) {
-        // TODO: we need to have a StMountOperation here to e.g. trigger
-        // shell dialogs when applications are blocking the mount.
+        let mountOp = new ShellMountOperation.ShellMountOperation(mount);
+
         if (mount.can_eject())
-            mount.eject_with_operation(0, null, null,
+            mount.eject_with_operation(0, mountOp.mountOp, null,
                                        Lang.bind(this, this._onMountEject));
         else
-            mount.unmount_with_operation(0, null, null,
+            mount.unmount_with_operation(0, mountOp.mountOp, null,
                                          Lang.bind(this, this._onMountEject));
     },
 
@@ -218,6 +219,9 @@ AutorunManager.prototype = {
             else
                 mount.unmount_with_operation_finish(res);
         } catch (e) {
+            // FIXME: we need to ignore G_IO_ERROR_FAILED_HANDLED errors here
+            // but we can't access the error code from JS.
+            // See https://bugzilla.gnome.org/show_bug.cgi?id=591480
             log('Unable to eject the mount ' + mount.get_name() 
                 + ': ' + e.toString());
         }
diff --git a/js/ui/shellMountOperation.js b/js/ui/shellMountOperation.js
new file mode 100644
index 0000000..900d13f
--- /dev/null
+++ b/js/ui/shellMountOperation.js
@@ -0,0 +1,249 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Lang = imports.lang;
+const Signals = imports.signals;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Pango = imports.gi.Pango;
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+
+const ModalDialog = imports.ui.modalDialog;
+
+function _setLabelText(label, text) {
+    if (text) {
+        label.set_text(text);
+        label.show();
+    } else {
+        label.set_text('');
+        label.hide();
+    }
+}
+
+function ListItem(app) {
+    this._init(app);
+}
+
+ListItem.prototype = {
+    _init: function(app) {
+        this._app = app;
+        this._ITEM_ICON_SIZE = 48;
+
+        let layout = new St.BoxLayout({ vertical: false});
+
+        this.actor = new St.Button({ style_class: 'show-processes-dialog-app-list-item',
+                                     can_focus: true,
+                                     child: layout,
+                                     reactive: true,
+                                     x_align: St.Align.START,
+                                     x_fill: true });
+
+        this._icon = this._app.create_icon_texture(this._ITEM_ICON_SIZE);
+
+        let iconBin = new St.Bin({ style_class: 'show-processes-dialog-app-list-item-icon',
+                                   child: this._icon });
+        layout.add(iconBin);
+
+        this._nameLabel = new St.Label({ text: this._app.get_name(),
+                                         style_class: 'show-processes-dialog-app-list-item-name' });
+        let labelBin = new St.Bin({ y_align: St.Align.MIDDLE,
+                                    child: this._nameLabel });
+        layout.add(labelBin);
+
+        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+    },
+
+    _onClicked: function() {
+        this.emit('activate');
+        this._app.activate(-1);
+    }
+};
+Signals.addSignalMethods(ListItem.prototype);
+
+function ShellMountOperation(mount) {
+    this._init(mount);
+}
+
+ShellMountOperation.prototype = {
+    _init: function(mount) {
+        this._initMountOp();
+        this._initIcon(mount);
+    },
+
+    _initMountOp: function() {
+        this.mountOp = new Shell.MountOperation();
+
+        this.mountOp.connect('ask-question',
+                             Lang.bind(this, this._onAskQuestion));
+        this.mountOp.connect('ask-password',
+                             Lang.bind(this, this._onAskPassword));
+        this.mountOp.connect('show-processes-2',
+                             Lang.bind(this, this._onShowProcesses2));
+        this.mountOp.connect('aborted',
+                             Lang.bind(this, this._onAborted));
+    },
+
+    _initIcon: function(mount) {
+        this._icon = new St.Icon({ gicon: mount.get_icon(),
+                                   style_class: 'shell-mount-operation-icon' });
+    },
+
+    _onAskQuestion: function(op, message, choices) {
+        // TODO
+    },
+
+    _onAskPassword: function(op, message, defaultUser, defaultDomain, flags) {
+        // TODO
+    },
+
+    _onAborted: function(op) {
+        // TODO
+    },
+
+    _onShowProcesses2: function(op) {
+        let processes = op.get_show_processes_pids();
+        let choices = op.get_show_processes_choices();
+        let message = op.get_show_processes_message();
+
+        if (!this._processesDialog) {
+            this._processesDialog = new ShellProcessesDialog(this._icon);
+            this._processesDialog.connect('choice-chosen', 
+                                          Lang.bind(this, function(object, choice) {
+                                              if (choice == -1) {
+                                                  this.mountOp.reply(Gio.MountOperationResult.ABORTED);
+                                              } else {
+                                                  this.mountOp.set_choice(choice);
+                                                  this.mountOp.reply(Gio.MountOperationResult.HANDLED);
+                                              }
+
+                                              this._processesDialog.close(global.get_current_time());
+                                          }));
+            this._processesDialog.open(global.get_current_time());
+        }
+
+        this._processesDialog.update(message, processes, choices);
+    },
+}
+
+function ShellProcessesDialog(icon) {
+    this._init(icon);
+}
+
+ShellProcessesDialog.prototype = {
+    __proto__: ModalDialog.ModalDialog.prototype,
+
+    _init: function(icon) {
+        ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'show-processes-dialog' });
+
+        this._buildUI(icon);
+    },
+
+    _buildUI: function(icon) {
+        let mainContentLayout = new St.BoxLayout();
+        this.contentLayout.add(mainContentLayout, { x_fill: true,
+                                                    y_fill: false });
+
+        this._iconBin = new St.Bin({ child: icon });
+        mainContentLayout.add(this._iconBin,
+                              { x_fill:  true,
+                                y_fill:  false,
+                                x_align: St.Align.END,
+                                y_align: St.Align.MIDDLE });
+
+        let messageLayout = new St.BoxLayout({ vertical: true });
+        mainContentLayout.add(messageLayout,
+                              { y_align: St.Align.START });
+
+        this._subjectLabel = new St.Label({ style_class: 'show-processes-dialog-subject' });
+
+        messageLayout.add(this._subjectLabel,
+                          { y_fill:  false,
+                            y_align: St.Align.START });
+
+        this._descriptionLabel = new St.Label({ style_class: 'show-processes-dialog-description' });
+        this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
+        this._descriptionLabel.clutter_text.line_wrap = true;
+
+        messageLayout.add(this._descriptionLabel,
+                          { y_fill:  true,
+                            y_align: St.Align.START });
+
+        let scrollView = new St.ScrollView({ style_class: 'show-processes-dialog-app-list'});
+        scrollView.set_policy(Gtk.PolicyType.NEVER,
+                              Gtk.PolicyType.AUTOMATIC);
+        this.contentLayout.add(scrollView,
+                               { x_fill: true,
+                                 y_fill: true });
+        scrollView.hide();
+
+        this._applicationList = new St.BoxLayout({ vertical: true });
+        scrollView.add_actor(this._applicationList,
+                             { x_fill:  true,
+                               y_fill:  true,
+                               x_align: St.Align.START,
+                               y_align: St.Align.MIDDLE });
+
+        this._applicationList.connect('actor-added',
+                                      Lang.bind(this, function() {
+                                          if (this._applicationList.get_children().length == 1)
+                                              scrollView.show();
+                                      }));
+
+        this._applicationList.connect('actor-removed',
+                                      Lang.bind(this, function() {
+                                          if (this._applicationList.get_children().length == 0)
+                                              scrollView.hide();
+                                      }));
+    },
+
+    _setButtonsForChoices: function(choices) {
+        let buttons = [];
+
+        for (let idx = 0; idx < choices.length; idx++) {
+            let button = idx;
+            buttons.unshift({ label: choices[idx],
+                              action: Lang.bind(this, function() {
+                                  this.emit('choice-chosen', button);
+                              })});
+        }
+
+        this.setButtons(buttons);
+    },
+
+    _setAppsForPids: function(pids) {
+        // remove all the items
+        this._applicationList.destroy_children();
+
+        pids.forEach(Lang.bind(this, function(pid) {
+            let tracker = Shell.WindowTracker.get_default();
+            let app = tracker.get_app_from_pid(pid);
+
+            if (!app)
+                return;
+
+            let item = new ListItem(app);
+            this._applicationList.add(item.actor, { x_fill: true });
+
+            item.connect('activate',
+                         Lang.bind(this, function() {
+                             // use -1 to indicate Cancel
+                             this.emit('choice-chosen', -1);
+                         }));
+        }));
+    },
+
+    _setLabelsForMessage: function(message) {
+        let labels = message.split('\n');
+
+        _setLabelText(this._subjectLabel, labels[0]);
+        if (labels.length > 1)
+            _setLabelText(this._descriptionLabel, labels[1]);
+    },
+
+    update: function(message, processes, choices) {
+        this._setLabelsForMessage(message);
+        this._setAppsForPids(processes);
+        this._setButtonsForChoices(choices);
+    }
+}
+Signals.addSignalMethods(ShellProcessesDialog.prototype);
\ No newline at end of file
diff --git a/src/Makefile.am b/src/Makefile.am
index ed679d5..337940e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -98,6 +98,7 @@ shell_public_headers_h =		\
 	shell-gtk-embed.h		\
 	shell-global.h			\
 	shell-mobile-providers.h	\
+	shell-mount-operation.h		\
 	shell-perf-log.h		\
 	shell-slicer.h			\
 	shell-stack.h			\
@@ -131,6 +132,7 @@ libgnome_shell_la_SOURCES =		\
 	shell-gtk-embed.c		\
 	shell-global.c			\
 	shell-mobile-providers.c	\
+	shell-mount-operation.c		\
 	shell-perf-log.c		\
 	shell-polkit-authentication-agent.h	\
 	shell-polkit-authentication-agent.c	\
diff --git a/src/shell-mount-operation.c b/src/shell-mount-operation.c
new file mode 100644
index 0000000..0d86ef9
--- /dev/null
+++ b/src/shell-mount-operation.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include "shell-mount-operation.h"
+
+/* This is a dummy class; we would like to be able to subclass the
+ * object from JS but we can't yet; the default GMountOperation impl
+ * automatically calls g_mount_operation_reply(UNHANDLED) after an idle,
+ * in interactive methods. We want to handle the reply outselves
+ * instead, so we just override the default methods with empty ones.
+ *
+ * Also, we need to workaround the fact that gjs doesn't support type
+ * annotations for signals yet (so we can't effectively forward e.g. 
+ * the GPid array to JS).
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=645978
+ */
+G_DEFINE_TYPE (ShellMountOperation, shell_mount_operation, G_TYPE_MOUNT_OPERATION);
+
+enum {
+  SHOW_PROCESSES_2,
+  NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+struct _ShellMountOperationPrivate {
+  GArray *pids;
+  gchar **choices;
+  gchar *message;
+};
+
+static void
+shell_mount_operation_init (ShellMountOperation *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_MOUNT_OPERATION,
+                                            ShellMountOperationPrivate);
+}
+
+static void
+shell_mount_operation_ask_password (GMountOperation   *op,
+                                    const char        *message,
+                                    const char        *default_user,
+                                    const char        *default_domain,
+                                    GAskPasswordFlags  flags)
+{
+  /* do nothing */
+}
+
+static void
+shell_mount_operation_ask_question (GMountOperation *op,
+                                    const char      *message,
+                                    const char      *choices[])
+{
+  /* do nothing */
+}
+
+static void
+shell_mount_operation_show_processes (GMountOperation *operation,
+                                      const gchar     *message,
+                                      GArray          *processes,
+                                      const gchar     *choices[])
+{
+  ShellMountOperation *self = SHELL_MOUNT_OPERATION (operation);
+
+  if (self->priv->pids != NULL)
+    {
+      g_array_unref (self->priv->pids);
+      self->priv->pids = NULL;
+    }
+
+  g_free (self->priv->message);
+  g_strfreev (self->priv->choices);
+
+  /* save the parameters */
+  self->priv->pids = g_array_ref (processes);
+  self->priv->choices = g_strdupv ((gchar **) choices);
+  self->priv->message = g_strdup (message);
+
+  g_signal_emit (self, signals[SHOW_PROCESSES_2], 0);
+}
+
+static void
+shell_mount_operation_finalize (GObject *obj)
+{
+  ShellMountOperation *self = SHELL_MOUNT_OPERATION (obj);
+
+  g_strfreev (self->priv->choices);
+  g_free (self->priv->message);
+
+  if (self->priv->pids != NULL)
+    {
+      g_array_unref (self->priv->pids);
+      self->priv->pids = NULL;
+    }
+
+  G_OBJECT_CLASS (shell_mount_operation_parent_class)->finalize (obj);
+}
+
+static void
+shell_mount_operation_class_init (ShellMountOperationClass *klass)
+{
+  GMountOperationClass *mclass;
+  GObjectClass *oclass;
+
+  mclass = G_MOUNT_OPERATION_CLASS (klass);
+  mclass->show_processes = shell_mount_operation_show_processes;
+  mclass->ask_question = shell_mount_operation_ask_question;
+  mclass->ask_password = shell_mount_operation_ask_password;
+
+  oclass = G_OBJECT_CLASS (klass);
+  oclass->finalize = shell_mount_operation_finalize;
+
+  signals[SHOW_PROCESSES_2] =
+    g_signal_new ("show-processes-2",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  g_type_class_add_private (klass, sizeof (ShellMountOperationPrivate));
+}
+
+GMountOperation *
+shell_mount_operation_new (void)
+{
+  return g_object_new (SHELL_TYPE_MOUNT_OPERATION, NULL);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_pids:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full) (element-type GPid): a #GArray
+ */
+GArray *
+shell_mount_operation_get_show_processes_pids (ShellMountOperation *self)
+{
+  return g_array_ref (self->priv->pids);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_choices:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar **
+shell_mount_operation_get_show_processes_choices (ShellMountOperation *self)
+{
+  return g_strdupv (self->priv->choices);
+}
+
+/**
+ * shell_mount_operation_get_show_processes_message:
+ * @self: a #ShellMountOperation
+ *
+ * Returns: (transfer full):
+ */
+gchar *
+shell_mount_operation_get_show_processes_message (ShellMountOperation *self)
+{
+  return g_strdup (self->priv->message);
+}
diff --git a/src/shell-mount-operation.h b/src/shell-mount-operation.h
new file mode 100644
index 0000000..222809c
--- /dev/null
+++ b/src/shell-mount-operation.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef __SHELL_MOUNT_OPERATION_H__
+#define __SHELL_MOUNT_OPERATION_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define SHELL_TYPE_MOUNT_OPERATION         (shell_mount_operation_get_type ())
+#define SHELL_MOUNT_OPERATION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperation))
+#define SHELL_MOUNT_OPERATION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperationClass))
+#define SHELL_IS_MOUNT_OPERATION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), SHELL_TYPE_MOUNT_OPERATION))
+#define SHELL_IS_MOUNT_OPERATION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), SHELL_TYPE_MOUNT_OPERATION))
+#define SHELL_MOUNT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SHELL_TYPE_MOUNT_OPERATION, ShellMountOperationClass))
+
+typedef struct _ShellMountOperation         ShellMountOperation;
+typedef struct _ShellMountOperationClass    ShellMountOperationClass;
+typedef struct _ShellMountOperationPrivate  ShellMountOperationPrivate;
+
+struct _ShellMountOperation
+{
+  GMountOperation parent_instance;
+
+  ShellMountOperationPrivate *priv;
+};
+
+struct _ShellMountOperationClass
+{
+  GMountOperationClass parent_class;
+};
+
+
+GType            shell_mount_operation_get_type   (void);
+GMountOperation *shell_mount_operation_new        (void);
+
+GArray * shell_mount_operation_get_show_processes_pids (ShellMountOperation *self);
+gchar ** shell_mount_operation_get_show_processes_choices (ShellMountOperation *self);
+gchar * shell_mount_operation_get_show_processes_message (ShellMountOperation *self);
+
+G_END_DECLS
+
+#endif /* __SHELL_MOUNT_OPERATION_H__ */



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