gnome-shell/js/ui/runDialog.js
Milan Bouchet-Valat 2799327c84 Migrate to GSettings
Use GSettings for all Shell configuration. GConf is kept to read
configuration from external programs (Metacity, Nautilus and Magnifier),
but ShellGConf is removed because it's mostly useless for the few calls
we still have. Also get rid of unused GConf code in ShellAppSystem.

A basic GConf schema is still used to override Metacity defaults and
configure Magnifier in a system-wide fashion. GConf is also used as
GSettings backend via the GSETTINGS_BACKEND environment variable.
All of this will be removed when these programs have been ported
to GSettings and able to use dconf.

GLib 2.25.9 is required. Schemas are converted to the new XML format,
and compiled at build time in data/ so that the Shell can be run from
the source tree. This also requires setting the GSETTINGS_SCHEMA_DIR
environment variable both when running installed or from source tree,
in src/gnome-shell.in and src/gnome-shell-clock-preferences.in.

https://bugzilla.gnome.org/show_bug.cgi?id=617917
2010-06-18 20:27:41 +02:00

454 lines
16 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const MAX_FILE_DELETED_BEFORE_INVALID = 10;
const HISTORY_KEY = 'command-history';
const HISTORY_LIMIT = 512;
const DIALOG_FADE_TIME = 0.1;
function CommandCompleter() {
this._init();
}
CommandCompleter.prototype = {
_init : function() {
this._changedCount = 0;
this._paths = GLib.getenv('PATH').split(':');
this._paths.push(GLib.get_home_dir());
this._valid = false;
this._updateInProgress = false;
this._childs = new Array(this._paths.length);
this._monitors = new Array(this._paths.length);
for (let i = 0; i < this._paths.length; i++) {
this._childs[i] = [];
let file = Gio.file_new_for_path(this._paths[i]);
let info;
try {
info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
} catch (e) {
// FIXME catchall
this._paths[i] = null;
continue;
}
if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY)
continue;
this._paths[i] = file.get_path();
this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null);
if (this._monitors[i] != null) {
this._monitors[i].connect('changed', Lang.bind(this, this._onChanged));
}
}
this._paths = this._paths.filter(function(a) {
return a != null;
});
this._update(0);
},
_onGetEnumerateComplete : function(obj, res) {
this._enumerator = obj.enumerate_children_finish(res);
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete));
},
_onNextFileComplete : function(obj, res) {
let files = obj.next_files_finish(res);
for (let i = 0; i < files.length; i++) {
this._childs[this._i].push(files[i].get_name());
}
if (files.length) {
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete));
} else {
this._enumerator.close(null);
this._enumerator = null;
this._update(this._i + 1);
}
},
update : function() {
if (this._valid)
return;
this._update(0);
},
_update : function(i) {
if (i == 0 && this._updateInProgress)
return;
this._updateInProgress = true;
this._changedCount = 0;
this._i = i;
if (i >= this._paths.length) {
this._valid = true;
this._updateInProgress = false;
return;
}
let file = Gio.file_new_for_path(this._paths[i]);
this._childs[this._i] = [];
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onGetEnumerateComplete));
},
_onChanged : function(m, f, of, type) {
if (!this._valid)
return;
let path = f.get_parent().get_path();
let k = undefined;
for (let i = 0; i < this._paths.length; i++) {
if (this._paths[i] == path)
k = i;
}
if (k === undefined) {
return;
}
if (type == Gio.FileMonitorEvent.CREATED) {
this._childs[k].push(f.get_basename());
}
if (type == Gio.FileMonitorEvent.DELETED) {
this._changedCount++;
if (this._changedCount > MAX_FILE_DELETED_BEFORE_INVALID) {
this._valid = false;
}
let name = f.get_basename();
this._childs[k] = this._childs[k].filter(function(e) {
return e != name;
});
}
if (type == Gio.FileMonitorEvent.UNMOUNTED) {
this._childs[k] = [];
}
},
getCompletion: function(text) {
let common = '';
let notInit = true;
if (!this._valid) {
this._update(0);
return common;
}
function _getCommon(s1, s2) {
let k = 0;
for (; k < s1.length && k < s2.length; k++) {
if (s1[k] != s2[k])
break;
}
if (k == 0)
return '';
return s1.substr(0, k);
}
function _hasPrefix(s1, prefix) {
return s1.indexOf(prefix) == 0;
}
for (let i = 0; i < this._childs.length; i++) {
for (let k = 0; k < this._childs[i].length; k++) {
if (!_hasPrefix(this._childs[i][k], text))
continue;
if (notInit) {
common = this._childs[i][k];
notInit = false;
}
common = _getCommon(common, this._childs[i][k]);
}
}
if (common.length)
return common.substr(text.length);
return common;
}
};
function RunDialog() {
this._init();
}
RunDialog.prototype = {
_init : function() {
this._isOpen = false;
global.settings.connect('changed::development-tools', Lang.bind(this, function () {
this._enableInternalCommands = global.settings.get_boolean('development-tools');
}));
this._enableInternalCommands = global.settings.get_boolean('development-tools');
this._history = global.settings.get_strv(HISTORY_KEY);
this._historyIndex = -1;
global.settings.connect('changed::' + HISTORY_KEY, Lang.bind(this, function() {
this._history = global.settings.get_strv(HISTORY_KEY);
this._historyIndex = this._history.length;
}));
this._internalCommands = { 'lg':
Lang.bind(this, function() {
Main.createLookingGlass().open();
}),
'r': Lang.bind(this, function() {
global.reexec_self();
}),
// Developer brain backwards compatibility
'restart': Lang.bind(this, function() {
global.reexec_self();
}),
'debugexit': Lang.bind(this, function() {
Meta.exit(Meta.ExitCode.ERROR);
})
};
// All actors are inside _group. We create it initially
// hidden then show it in show()
this._group = new Clutter.Group({ visible: false,
x: 0, y: 0 });
Main.uiGroup.add_actor(this._group);
this._lightbox = new Lightbox.Lightbox(this._group,
{ inhibitEvents: true });
this._box = new St.Bin({ x_align: St.Align.MIDDLE,
y_align: St.Align.MIDDLE });
this._group.add_actor(this._box);
this._lightbox.highlight(this._box);
let dialogBox = new St.BoxLayout({ style_class: 'run-dialog', vertical: true });
this._box.set_child(dialogBox);
let label = new St.Label({ style_class: 'run-dialog-label',
text: _("Please enter a command:") });
dialogBox.add(label, { expand: true, y_fill: false });
let entry = new St.Entry({ style_class: 'run-dialog-entry' });
this._entryText = entry.clutter_text;
dialogBox.add(entry, { expand: true });
this._errorBox = new St.BoxLayout();
dialogBox.add(this._errorBox, { expand: true });
let errorIcon = new St.Button({ style_class: 'run-dialog-error-icon' });
this._errorBox.add(errorIcon);
this._commandError = false;
this._errorMessage = new St.Label({ style_class: 'run-dialog-error-label' });
this._errorMessage.clutter_text.line_wrap = true;
this._errorBox.add(this._errorMessage, { expand: true });
this._errorBox.hide();
this._pathCompleter = new Gio.FilenameCompleter();
this._commandCompleter = new CommandCompleter();
this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update));
this._entryText.connect('key-press-event', Lang.bind(this, function(o, e) {
let symbol = e.get_key_symbol();
if (symbol == Clutter.Down) {
this._setCommandFromHistory(this._historyIndex++);
return true;
}
if (symbol == Clutter.Up) {
this._setCommandFromHistory(this._historyIndex--);
return true;
}
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
if (Shell.get_event_state(e) & Clutter.ModifierType.CONTROL_MASK)
this._run(o.get_text(), true);
else
this._run(o.get_text(), false);
if (!this._commandError)
this.close();
}
if (symbol == Clutter.Escape) {
this.close();
return true;
}
if (symbol == Clutter.slash) {
// Need preload data before get completion. GFilenameCompleter load content of parent directory.
// Parent directory for /usr/include/ is /usr/. So need to add fake name('a').
let text = o.get_text().concat('/a');
let prefix;
if (text.lastIndexOf(' ') == -1)
prefix = text;
else
prefix = text.substr(text.lastIndexOf(' ') + 1);
this._getCompletion(prefix);
return false;
}
if (symbol == Clutter.Tab) {
let text = o.get_text();
let prefix;
if (text.lastIndexOf(' ') == -1)
prefix = text;
else
prefix = text.substr(text.lastIndexOf(' ') + 1);
let postfix = this._getCompletion(prefix);
if (postfix != null && postfix.length > 0) {
o.insert_text(postfix, -1);
o.set_cursor_position(text.length + postfix.length);
if (postfix[postfix.length - 1] == '/')
this._getCompletion(text + postfix + 'a');
}
return true;
}
return false;
}));
},
_getCompletion : function(text) {
if (text.indexOf('/') != -1) {
return this._pathCompleter.get_completion_suffix(text);
} else {
return this._commandCompleter.getCompletion(text);
}
},
_saveHistory : function() {
if (this._history.length > HISTORY_LIMIT) {
this._history.splice(0, this._history.length - HISTORY_LIMIT);
}
global.settings.set_strv(HISTORY_KEY, this._history);
},
_run : function(input, inTerminal) {
let command = input;
if (this._history.length == 0 ||
this._history[this._history.length - 1] != input) {
this._history.push(input);
this._saveHistory();
}
this._commandError = false;
let f;
if (this._enableInternalCommands)
f = this._internalCommands[input];
else
f = null;
if (f) {
f();
} else if (input) {
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();
} catch (e) {
// Mmmh, that failed - see if @input matches an existing file
let path = null;
if (input.charAt(0) == '/') {
path = input;
} else {
if (input.charAt(0) == '~')
input = input.slice(1);
path = GLib.get_home_dir() + '/' + input;
}
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
let file = Gio.file_new_for_path(path);
Gio.app_info_launch_default_for_uri(file.get_uri(),
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];
this._errorMessage.set_text(errorStr);
this._errorBox.show();
}
}
}
},
_setCommandFromHistory: function(lastI) {
if (this._historyIndex < 0)
this._historyIndex = 0;
if (this._historyIndex > this._history.length)
this._historyIndex = this._history.length;
let text = this._entryText.get_text();
if (text) {
this._history[lastI] = text;
}
if (this._history[this._historyIndex]) {
this._entryText.set_text(this._history[this._historyIndex]);
} else
this._entryText.set_text('');
},
open : function() {
if (this._isOpen) // Already shown
return;
if (!Main.pushModal(this._group))
return;
// Position the dialog on the current monitor
let monitor = global.get_focus_monitor();
this._historyIndex = this._history.length;
this._box.set_position(monitor.x, monitor.y);
this._box.set_size(monitor.width, monitor.height);
this._isOpen = true;
this._lightbox.show();
this._group.opacity = 0;
this._group.show();
Tweener.addTween(this._group,
{ opacity: 255,
time: DIALOG_FADE_TIME,
transition: 'easeOutQuad'
});
global.stage.set_key_focus(this._entryText);
},
close : function() {
if (!this._isOpen)
return;
this._isOpen = false;
this._commandError = false;
Main.popModal(this._group);
Tweener.addTween(this._group,
{ opacity: 0,
time: DIALOG_FADE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
this._errorBox.hide();
this._group.hide();
this._entryText.set_text('');
})
});
}
};
Signals.addSignalMethods(RunDialog.prototype);