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
This commit is contained in:
Dan Winship 2010-11-17 11:43:08 -05:00
parent a65a0f03d4
commit 8bdfb8df68
10 changed files with 158 additions and 42 deletions

View File

@ -1,5 +1,16 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- 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 */ /* 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'); 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 }); res.push({ url: match[0], pos: match.index });
return res; 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);
}

View File

@ -39,6 +39,7 @@ const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier; const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler; const XdndHandler = imports.ui.xdndHandler;
const StatusIconDispatcher = imports.ui.statusIconDispatcher; const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const Util = imports.misc.util;
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color(); const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff); DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
@ -317,11 +318,8 @@ function _globalKeyPressHandler(actor, event) {
if (action == Meta.KeyBindingAction.COMMAND_SCREENSHOT) { if (action == Meta.KeyBindingAction.COMMAND_SCREENSHOT) {
let gconf = GConf.Client.get_default(); let gconf = GConf.Client.get_default();
let command = gconf.get_string('/apps/metacity/keybinding_commands/command_screenshot'); let command = gconf.get_string('/apps/metacity/keybinding_commands/command_screenshot');
if (command != null && command != '') { if (command != null && command != '')
let [ok, len, args] = GLib.shell_parse_argv(command); Util.spawnCommandLine(command);
let p = new Shell.Process({'args' : args});
p.run();
}
return true; return true;
} }

View File

@ -106,8 +106,7 @@ URLHighlighter.prototype = {
return true; return true;
} catch (e) { } catch (e) {
// TODO: remove this after gnome 3 release // TODO: remove this after gnome 3 release
let p = new Shell.Process({ 'args' : ['gvfs-open', url] }); Util.spawn(['gvfs-open', url]);
p.run();
return true; return true;
} }
} }

View File

@ -14,6 +14,7 @@ const _ = Gettext.gettext;
const DND = imports.ui.dnd; const DND = imports.ui.dnd;
const Main = imports.ui.main; const Main = imports.ui.main;
const Search = imports.ui.search; const Search = imports.ui.search;
const Util = imports.misc.util;
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences'; const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir'; const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@ -163,7 +164,7 @@ PlacesManager.prototype = {
icon_size: size }); icon_size: size });
}, },
function () { function () {
new Shell.Process({ args: ['nautilus-connect-server'] }).run(); Util.spawn(['nautilus-connect-server']);
}); });
let networkApp = null; let networkApp = null;

View File

@ -14,6 +14,7 @@ const _ = Gettext.gettext;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main; const Main = imports.ui.main;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Util = imports.misc.util;
const MAX_FILE_DELETED_BEFORE_INVALID = 10; const MAX_FILE_DELETED_BEFORE_INVALID = 10;
@ -354,9 +355,7 @@ RunDialog.prototype = {
try { try {
if (inTerminal) if (inTerminal)
command = 'gnome-terminal -x ' + input; command = 'gnome-terminal -x ' + input;
let [ok, len, args] = GLib.shell_parse_argv(command); Util.trySpawnCommandLine(command);
let p = new Shell.Process({ 'args' : args });
p.run();
} catch (e) { } catch (e) {
// Mmmh, that failed - see if @input matches an existing file // Mmmh, that failed - see if @input matches an existing file
let path = null; let path = null;
@ -374,13 +373,8 @@ RunDialog.prototype = {
global.create_app_launch_context()); global.create_app_launch_context());
} else { } else {
this._commandError = true; this._commandError = true;
// The exception contains an error string like:
// Error invoking Shell.run: Failed to execute child let errorStr = _("Execution of '%s' failed:").format(command) + '\n' + e.message;
// 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];
this._errorMessage.set_text(errorStr); this._errorMessage.set_text(errorStr);
this._errorBox.show(); this._errorBox.show();

View File

@ -12,6 +12,7 @@ const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
@ -108,8 +109,7 @@ ATIndicator.prototype = {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addAction(_("Universal Access Settings"), function() { this.menu.addAction(_("Universal Access Settings"), function() {
let p = new Shell.Process({ args: ['gnome-control-center','universal-access'] }); Util.spawnDesktop('gnome-universal-access-panel');
p.run();
}); });
}, },

View File

@ -10,6 +10,7 @@ const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
@ -83,7 +84,7 @@ Indicator.prototype = {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addAction(_("Power Settings"),function() { 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)); this._proxy.connect('Changed', Lang.bind(this, this._devicesChanged));
@ -134,8 +135,7 @@ Indicator.prototype = {
this._batteryItem.actor.reactive = true; this._batteryItem.actor.reactive = true;
this._batteryItem.actor.can_focus = true; this._batteryItem.actor.can_focus = true;
this._batteryItem.connect('activate', function(item) { this._batteryItem.connect('activate', function(item) {
let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] }); Util.spawn(['gnome-power-statistics', '--device', device_id]);
p.run();
}); });
} else { } else {
// virtual device // virtual device
@ -164,8 +164,7 @@ Indicator.prototype = {
let item = new DeviceItem (devices[i]); let item = new DeviceItem (devices[i]);
item.connect('activate', function() { item.connect('activate', function() {
let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] }); Util.spawn(['gnome-power-statistics', '--device', device_id]);
p.run();
}); });
this._deviceItems.push(item); this._deviceItems.push(item);
this.menu.addMenuItem(item, this._otherDevicePosition + position); this.menu.addMenuItem(item, this._otherDevicePosition + position);
@ -198,7 +197,7 @@ Indicator.prototype = {
_checkError: function(error) { _checkError: function(error) {
if (!this._restarted && error && error.message.match(/org\.freedesktop\.DBus\.Error\.(UnknownMethod|InvalidArgs)/)) { 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_sync('pkill -f "^gnome-power-manager$"');
GLib.spawn_command_line_async('gnome-power-manager'); Util.spawn(['gnome-power-manager']);
this._restarted = true; this._restarted = true;
} }
} }

View File

@ -11,6 +11,7 @@ const St = imports.gi.St;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
@ -60,8 +61,7 @@ Indicator.prototype = {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addAction(_("Sound Settings"), function() { this.menu.addAction(_("Sound Settings"), function() {
let p = new Shell.Process({ args: ['gnome-control-center', 'sound'] }); Util.spawnDesktop('gnome-sound-panel');
p.run();
}); });
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));

View File

@ -12,6 +12,7 @@ const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main; const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
// Adapted from gdm/gui/user-switch-applet/applet.c // Adapted from gdm/gui/user-switch-applet/applet.c
// //
@ -31,7 +32,7 @@ StatusMenuButton.prototype = {
this.actor.set_child(box); this.actor.set_child(box);
this._gdm = Gdm.UserManager.ref_default(); 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._user = this._gdm.get_user(GLib.get_user_name());
this._presence = new GnomeSession.Presence(); this._presence = new GnomeSession.Presence();
@ -153,17 +154,17 @@ StatusMenuButton.prototype = {
_onMyAccountActivate: function() { _onMyAccountActivate: function() {
Main.overview.hide(); Main.overview.hide();
this._spawn(['gnome-control-center', 'user-accounts']); Util.spawnDesktop('gnome-user-accounts-panel');
}, },
_onPreferencesActivate: function() { _onPreferencesActivate: function() {
Main.overview.hide(); Main.overview.hide();
this._spawn(['gnome-control-center', '-o']); Util.spawnDesktop('gnome-control-center');
}, },
_onLockScreenActivate: function() { _onLockScreenActivate: function() {
Main.overview.hide(); Main.overview.hide();
this._spawn(['gnome-screensaver-command', '--lock']); Util.spawn(['gnome-screensaver-command', '--lock']);
}, },
_onLoginScreenActivate: function() { _onLoginScreenActivate: function() {
@ -174,19 +175,11 @@ StatusMenuButton.prototype = {
_onQuitSessionActivate: function() { _onQuitSessionActivate: function() {
Main.overview.hide(); Main.overview.hide();
this._spawn(['gnome-session-save', '--logout-dialog']); Util.spawn(['gnome-session-save', '--logout-dialog']);
}, },
_onShutDownActivate: function() { _onShutDownActivate: function() {
Main.overview.hide(); Main.overview.hide();
this._spawn(['gnome-session-save', '--shutdown-dialog']); Util.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();
} }
}; };

View File

@ -1,6 +1,7 @@
data/gnome-shell.desktop.in.in data/gnome-shell.desktop.in.in
data/org.gnome.shell.gschema.xml.in data/org.gnome.shell.gschema.xml.in
data/org.gnome.accessibility.magnifier.gschema.xml.in data/org.gnome.accessibility.magnifier.gschema.xml.in
js/misc/util.js
js/ui/appDisplay.js js/ui/appDisplay.js
js/ui/appFavorites.js js/ui/appFavorites.js
js/ui/dash.js js/ui/dash.js