[gnome-shell] util: add Util.spawn and friends



commit 8bdfb8df6897a81a0c0087c6f778fe07902dd8f7
Author: Dan Winship <danw gnome org>
Date:   Wed Nov 17 11:43:08 2010 -0500

    util: add Util.spawn and friends
    
    Add Util.spawn, Util.spawnCommandLine, and Util.spawnDesktop for
    spawning a command/argv/.desktop file in the background, automatically
    handling errors via MessageTray.SystemNotificationSource(), and
    Util.trySpawn, Util.trySpawnCommandLine, and Utils.trySpawnDesktop
    that don't do automatic error handling (but do at least clean up the
    error message in the exception a bit).
    
    Update various other bits of code around the shell to use the new
    methods.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=635089

 js/misc/util.js               |  131 +++++++++++++++++++++++++++++++++++++++++
 js/ui/main.js                 |    8 +--
 js/ui/messageTray.js          |    3 +-
 js/ui/placeDisplay.js         |    3 +-
 js/ui/runDialog.js            |   14 +---
 js/ui/status/accessibility.js |    4 +-
 js/ui/status/power.js         |   11 ++--
 js/ui/status/volume.js        |    4 +-
 js/ui/statusMenu.js           |   21 ++-----
 po/POTFILES.in                |    1 +
 10 files changed, 158 insertions(+), 42 deletions(-)
---
diff --git a/js/misc/util.js b/js/misc/util.js
index 0d1d5c4..e61f0dd 100644
--- a/js/misc/util.js
+++ b/js/misc/util.js
@@ -1,5 +1,16 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Shell = imports.gi.Shell;
+
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
 /* http://daringfireball.net/2010/07/improved_regex_for_matching_urls */
 const _urlRegexp = new RegExp('\b(([a-z][\w-]+:(/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)([^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»â??â??â??â??]))', 'gi');
 
@@ -17,3 +28,123 @@ function findUrls(str) {
         res.push({ url: match[0], pos: match.index });
     return res;
 }
+
+// spawn:
+// @argv: an argv array
+//
+// Runs @argv in the background, handling any errors that occur
+// when trying to start the program.
+function spawn(argv) {
+    try {
+        trySpawn(argv);
+    } catch (err) {
+        _handleSpawnError(argv[0], err);
+    }
+}
+
+// spawnCommandLine:
+// @command_line: a command line
+//
+// Runs @command_line in the background, handling any errors that
+// occur when trying to parse or start the program.
+function spawnCommandLine(command_line) {
+    try {
+        let [success, argc, argv] = GLib.shell_parse_argv(command_line);
+        trySpawn(argv);
+    } catch (err) {
+        _handleSpawnError(command_line, err);
+    }
+}
+
+// spawnDesktop:
+// @id: a desktop file ID
+//
+// Spawns the desktop file identified by @id using startup notification,
+// etc, handling any errors that occur when trying to find or start
+// the program.
+function spawnDesktop(id) {
+    try {
+        trySpawnDesktop(id);
+    } catch (err) {
+        _handleSpawnError(id, err);
+    }
+}
+
+// trySpawn:
+// @argv: an argv array
+//
+// Runs @argv in the background. If launching @argv fails,
+// this will throw an error.
+function trySpawn(argv)
+{
+    try {
+        GLib.spawn_async(null, argv, null,
+                         GLib.SpawnFlags.SEARCH_PATH,
+                         null, null);
+    } catch (err) {
+        // The exception from gjs contains an error string like:
+        //   Error invoking GLib.spawn_command_line_async: Failed to
+        //   execute child process "foo" (No such file or directory)
+        // We are only interested in the part in the parentheses. (And
+        // we can't pattern match the text, since it gets localized.)
+        err.message = err.message.replace(/.*\((.+)\)/, '$1');
+        throw err;
+    }
+}
+
+// trySpawnCommandLine:
+// @command_line: a command line
+//
+// Runs @command_line in the background. If launching @command_line
+// fails, this will throw an error.
+function trySpawnCommandLine(command_line) {
+    let success, argc, argv;
+
+    try {
+        [success, argc, argv] = GLib.shell_parse_argv(command_line);
+    } catch (err) {
+        // Replace "Error invoking GLib.shell_parse_argv: " with
+        // something nicer
+        err.message = err.message.replace(/[^:]*: /, _("Could not parse command:") + "\n");
+        throw err;
+    }
+
+    trySpawn(argv);
+}
+
+// trySpawnDesktop:
+// @id: a desktop file ID
+//
+// Spawns the desktop file identified by @id using startup notification.
+// On error, throws an exception.
+function trySpawnDesktop(id) {
+    let app;
+
+    // shell_app_system_load_from_desktop_file() will end up returning
+    // a stupid error message if the desktop file doesn't exist, but
+    // that's the only case it returns an error for, so we just
+    // substitute our own error in instead
+    try {
+        app = Shell.AppSystem.get_default().load_from_desktop_file(id + '.desktop');
+    } catch (err) {
+        throw new Error(_("No such application"));
+    }
+
+    try {
+        app.launch();
+    } catch(err) {
+        // see trySpawn
+        err.message = err.message.replace(/.*\((.+)\)/, '$1');
+        throw err;
+    }
+}
+
+function _handleSpawnError(command, err) {
+    let title = _("Execution of '%s' failed:").format(command);
+
+    let source = new MessageTray.SystemNotificationSource();
+    Main.messageTray.add(source);
+    let notification = new MessageTray.Notification(source, title, err.message);
+    notification.setTransient(true);
+    source.notify(notification);
+}
diff --git a/js/ui/main.js b/js/ui/main.js
index 8574fe7..1ee8065 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -39,6 +39,7 @@ const WindowManager = imports.ui.windowManager;
 const Magnifier = imports.ui.magnifier;
 const XdndHandler = imports.ui.xdndHandler;
 const StatusIconDispatcher = imports.ui.statusIconDispatcher;
+const Util = imports.misc.util;
 
 const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
 DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
@@ -317,11 +318,8 @@ function _globalKeyPressHandler(actor, event) {
     if (action == Meta.KeyBindingAction.COMMAND_SCREENSHOT) {
         let gconf = GConf.Client.get_default();
         let command = gconf.get_string('/apps/metacity/keybinding_commands/command_screenshot');
-        if (command != null && command != '') {
-            let [ok, len, args] = GLib.shell_parse_argv(command);
-            let p = new Shell.Process({'args' : args});
-            p.run();
-        }
+        if (command != null && command != '')
+            Util.spawnCommandLine(command);
         return true;
     }
 
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index e085421..57f5acf 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -106,8 +106,7 @@ URLHighlighter.prototype = {
                     return true;
                 } catch (e) {
                     // TODO: remove this after gnome 3 release
-                    let p = new Shell.Process({ 'args' : ['gvfs-open', url] });
-                    p.run();
+                    Util.spawn(['gvfs-open', url]);
                     return true;
                 }
             }
diff --git a/js/ui/placeDisplay.js b/js/ui/placeDisplay.js
index fb96149..9b7134b 100644
--- a/js/ui/placeDisplay.js
+++ b/js/ui/placeDisplay.js
@@ -14,6 +14,7 @@ const _ = Gettext.gettext;
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const Search = imports.ui.search;
+const Util = imports.misc.util;
 
 const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
 const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@@ -163,7 +164,7 @@ PlacesManager.prototype = {
                                      icon_size: size });
             },
             function () {
-                new Shell.Process({ args: ['nautilus-connect-server'] }).run();
+                Util.spawn(['nautilus-connect-server']);
             });
 
         let networkApp = null;
diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js
index d09639c..955f1e7 100644
--- a/js/ui/runDialog.js
+++ b/js/ui/runDialog.js
@@ -14,6 +14,7 @@ const _ = Gettext.gettext;
 const Lightbox = imports.ui.lightbox;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
+const Util = imports.misc.util;
 
 const MAX_FILE_DELETED_BEFORE_INVALID = 10;
 
@@ -354,9 +355,7 @@ RunDialog.prototype = {
             try {
                 if (inTerminal)
                     command = 'gnome-terminal -x ' + input;
-                let [ok, len, args] = GLib.shell_parse_argv(command);
-                let p = new Shell.Process({ 'args' : args });
-                p.run();
+                Util.trySpawnCommandLine(command);
             } catch (e) {
                 // Mmmh, that failed - see if @input matches an existing file
                 let path = null;
@@ -374,13 +373,8 @@ RunDialog.prototype = {
                                                         global.create_app_launch_context());
                 } else {
                     this._commandError = true;
-                    // The exception contains an error string like:
-                    // Error invoking Shell.run: Failed to execute child
-                    // process "foo" (No such file or directory)
-                    // We are only interested in the actual error, so parse
-                    //that out.
-                    let m = /.+\((.+)\)/.exec(e);
-                    let errorStr = _("Execution of '%s' failed:").format(command) + '\n' + m[1];
+
+                    let errorStr = _("Execution of '%s' failed:").format(command) + '\n' + e.message;
                     this._errorMessage.set_text(errorStr);
 
                     this._errorBox.show();
diff --git a/js/ui/status/accessibility.js b/js/ui/status/accessibility.js
index f96e193..11a95f3 100644
--- a/js/ui/status/accessibility.js
+++ b/js/ui/status/accessibility.js
@@ -12,6 +12,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -108,8 +109,7 @@ ATIndicator.prototype = {
 
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
         this.menu.addAction(_("Universal Access Settings"), function() {
-            let p = new Shell.Process({ args: ['gnome-control-center','universal-access'] });
-            p.run();
+            Util.spawnDesktop('gnome-universal-access-panel');
         });
     },
 
diff --git a/js/ui/status/power.js b/js/ui/status/power.js
index 3a909c4..fe787f7 100644
--- a/js/ui/status/power.js
+++ b/js/ui/status/power.js
@@ -10,6 +10,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -83,7 +84,7 @@ Indicator.prototype = {
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
         this.menu.addAction(_("Power Settings"),function() {
-            GLib.spawn_command_line_async('gnome-control-center power');
+            Util.spawnDesktop('gnome-power-panel');
         });
 
         this._proxy.connect('Changed', Lang.bind(this, this._devicesChanged));
@@ -134,8 +135,7 @@ Indicator.prototype = {
                 this._batteryItem.actor.reactive = true;
                 this._batteryItem.actor.can_focus = true;
                 this._batteryItem.connect('activate', function(item) {
-                    let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] });
-                    p.run();
+                    Util.spawn(['gnome-power-statistics', '--device', device_id]);
                 });
             } else {
                 // virtual device
@@ -164,8 +164,7 @@ Indicator.prototype = {
 
                 let item = new DeviceItem (devices[i]);
                 item.connect('activate', function() {
-                    let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] });
-                    p.run();
+                    Util.spawn(['gnome-power-statistics', '--device', device_id]);
                 });
                 this._deviceItems.push(item);
                 this.menu.addMenuItem(item, this._otherDevicePosition + position);
@@ -198,7 +197,7 @@ Indicator.prototype = {
     _checkError: function(error) {
         if (!this._restarted && error && error.message.match(/org\.freedesktop\.DBus\.Error\.(UnknownMethod|InvalidArgs)/)) {
             GLib.spawn_command_line_sync('pkill -f "^gnome-power-manager$"');
-            GLib.spawn_command_line_async('gnome-power-manager');
+            Util.spawn(['gnome-power-manager']);
             this._restarted = true;
         }
     }
diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js
index 63f76fe..9211968 100644
--- a/js/ui/status/volume.js
+++ b/js/ui/status/volume.js
@@ -11,6 +11,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -60,8 +61,7 @@ Indicator.prototype = {
 
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
         this.menu.addAction(_("Sound Settings"), function() {
-            let p = new Shell.Process({ args: ['gnome-control-center', 'sound'] });
-            p.run();
+            Util.spawnDesktop('gnome-sound-panel');
         });
 
         this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
diff --git a/js/ui/statusMenu.js b/js/ui/statusMenu.js
index e2d13c6..f834aad 100644
--- a/js/ui/statusMenu.js
+++ b/js/ui/statusMenu.js
@@ -12,6 +12,7 @@ const GnomeSession = imports.misc.gnomeSession;
 const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 // Adapted from gdm/gui/user-switch-applet/applet.c
 //
@@ -31,7 +32,7 @@ StatusMenuButton.prototype = {
         this.actor.set_child(box);
 
         this._gdm = Gdm.UserManager.ref_default();
-        this._gdm.queue_load()
+        this._gdm.queue_load();
 
         this._user = this._gdm.get_user(GLib.get_user_name());
         this._presence = new GnomeSession.Presence();
@@ -153,17 +154,17 @@ StatusMenuButton.prototype = {
 
     _onMyAccountActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-control-center', 'user-accounts']);
+        Util.spawnDesktop('gnome-user-accounts-panel');
     },
 
     _onPreferencesActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-control-center', '-o']);
+        Util.spawnDesktop('gnome-control-center');
     },
 
     _onLockScreenActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-screensaver-command', '--lock']);
+        Util.spawn(['gnome-screensaver-command', '--lock']);
     },
 
     _onLoginScreenActivate: function() {
@@ -174,19 +175,11 @@ StatusMenuButton.prototype = {
 
     _onQuitSessionActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-session-save', '--logout-dialog']);
+        Util.spawn(['gnome-session-save', '--logout-dialog']);
     },
 
     _onShutDownActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-session-save', '--shutdown-dialog']);
-    },
-
-    _spawn: function(args) {
-        // FIXME: once Shell.Process gets support for signalling
-        // errors we should pop up an error dialog or something here
-        // on failure
-        let p = new Shell.Process({'args' : args});
-        p.run();
+        Util.spawn(['gnome-session-save', '--shutdown-dialog']);
     }
 };
diff --git a/po/POTFILES.in b/po/POTFILES.in
index cb9c39f..255a3d6 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,6 +1,7 @@
 data/gnome-shell.desktop.in.in
 data/org.gnome.shell.gschema.xml.in
 data/org.gnome.accessibility.magnifier.gschema.xml.in
+js/misc/util.js
 js/ui/appDisplay.js
 js/ui/appFavorites.js
 js/ui/dash.js



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