c9296191a8
clutter_actor_get_children requires making a temporary GSList from a linked list structure, and then creating a JS Array from that GSList. For simple cases like the number of children, use clutter_actor_get_n_children. https://bugzilla.gnome.org/show_bug.cgi?id=677426
390 lines
14 KiB
JavaScript
390 lines
14 KiB
JavaScript
// -*- mode: js; js-indent-level: 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 Main = imports.ui.main;
|
|
const MessageTray = imports.ui.messageTray;
|
|
const ModalDialog = imports.ui.modalDialog;
|
|
const Params = imports.misc.params;
|
|
|
|
const LIST_ITEM_ICON_SIZE = 48;
|
|
|
|
/* ------ Common Utils ------- */
|
|
function _setLabelText(label, text) {
|
|
if (text) {
|
|
label.set_text(text);
|
|
label.show();
|
|
} else {
|
|
label.set_text('');
|
|
label.hide();
|
|
}
|
|
}
|
|
|
|
function _setButtonsForChoices(dialog, choices) {
|
|
let buttons = [];
|
|
|
|
for (let idx = 0; idx < choices.length; idx++) {
|
|
let button = idx;
|
|
buttons.unshift({ label: choices[idx],
|
|
action: Lang.bind(dialog, function() {
|
|
dialog.emit('response', button);
|
|
})});
|
|
}
|
|
|
|
dialog.setButtons(buttons);
|
|
}
|
|
|
|
function _setLabelsForMessage(dialog, message) {
|
|
let labels = message.split('\n');
|
|
|
|
_setLabelText(dialog.subjectLabel, labels[0]);
|
|
if (labels.length > 1)
|
|
_setLabelText(dialog.descriptionLabel, labels[1]);
|
|
}
|
|
|
|
/* -------------------------------------------------------- */
|
|
|
|
const ListItem = new Lang.Class({
|
|
Name: 'ListItem',
|
|
|
|
_init: function(app) {
|
|
this._app = app;
|
|
|
|
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(LIST_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();
|
|
}
|
|
});
|
|
Signals.addSignalMethods(ListItem.prototype);
|
|
|
|
const ShellMountOperation = new Lang.Class({
|
|
Name: 'ShellMountOperation',
|
|
|
|
_init: function(source, params) {
|
|
params = Params.parse(params, { reaskPassword: false });
|
|
|
|
this._reaskPassword = params.reaskPassword;
|
|
|
|
this._dialog = null;
|
|
this._processesDialog = null;
|
|
|
|
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));
|
|
|
|
this._icon = new St.Icon({ gicon: source.get_icon(),
|
|
style_class: 'shell-mount-operation-icon' });
|
|
},
|
|
|
|
_onAskQuestion: function(op, message, choices) {
|
|
this._dialog = new ShellMountQuestionDialog(this._icon);
|
|
|
|
this._dialog.connect('response',
|
|
Lang.bind(this, function(object, choice) {
|
|
this.mountOp.set_choice(choice);
|
|
this.mountOp.reply(Gio.MountOperationResult.HANDLED);
|
|
|
|
this._dialog.close(global.get_current_time());
|
|
this._dialog = null;
|
|
}));
|
|
|
|
this._dialog.update(message, choices);
|
|
this._dialog.open(global.get_current_time());
|
|
},
|
|
|
|
_onAskPassword: function(op, message) {
|
|
this._notificationShowing = true;
|
|
this._source = new ShellMountPasswordSource(message, this._icon, this._reaskPassword);
|
|
|
|
this._source.connect('password-ready',
|
|
Lang.bind(this, function(source, password) {
|
|
this.mountOp.set_password(password);
|
|
this.mountOp.reply(Gio.MountOperationResult.HANDLED);
|
|
|
|
this._notificationShowing = false;
|
|
this._source.destroy();
|
|
}));
|
|
|
|
this._source.connect('destroy',
|
|
Lang.bind(this, function() {
|
|
if (!this._notificationShowing)
|
|
return;
|
|
|
|
this._notificationShowing = false;
|
|
this.mountOp.reply(Gio.MountOperationResult.ABORTED);
|
|
}));
|
|
},
|
|
|
|
_onAborted: function(op) {
|
|
if (!this._dialog)
|
|
return;
|
|
|
|
this._dialog.close(global.get_current_time());
|
|
this._dialog = null;
|
|
},
|
|
|
|
_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._dialog = this._processesDialog;
|
|
|
|
this._processesDialog.connect('response',
|
|
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._dialog = null;
|
|
}));
|
|
this._processesDialog.open(global.get_current_time());
|
|
}
|
|
|
|
this._processesDialog.update(message, processes, choices);
|
|
},
|
|
});
|
|
|
|
const ShellMountQuestionDialog = new Lang.Class({
|
|
Name: 'ShellMountQuestionDialog',
|
|
Extends: ModalDialog.ModalDialog,
|
|
|
|
_init: function(icon) {
|
|
this.parent({ styleClass: 'mount-question-dialog' });
|
|
|
|
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: 'mount-question-dialog-subject' });
|
|
this.subjectLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
this.subjectLabel.clutter_text.line_wrap = true;
|
|
|
|
messageLayout.add(this.subjectLabel,
|
|
{ y_fill: false,
|
|
y_align: St.Align.START });
|
|
|
|
this.descriptionLabel = new St.Label({ style_class: 'mount-question-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 });
|
|
},
|
|
|
|
update: function(message, choices) {
|
|
_setLabelsForMessage(this, message);
|
|
_setButtonsForChoices(this, choices);
|
|
}
|
|
});
|
|
Signals.addSignalMethods(ShellMountQuestionDialog.prototype);
|
|
|
|
const ShellMountPasswordSource = new Lang.Class({
|
|
Name: 'ShellMountPasswordSource',
|
|
Extends: MessageTray.Source,
|
|
|
|
_init: function(message, icon, reaskPassword) {
|
|
let strings = message.split('\n');
|
|
this.parent(strings[0]);
|
|
|
|
this._notification = new ShellMountPasswordNotification(this, strings, icon, reaskPassword);
|
|
this._setSummaryIcon(icon);
|
|
|
|
// add ourselves as a source, and popup the notification
|
|
Main.messageTray.add(this);
|
|
this.notify(this._notification);
|
|
},
|
|
});
|
|
Signals.addSignalMethods(ShellMountPasswordSource.prototype);
|
|
|
|
const ShellMountPasswordNotification = new Lang.Class({
|
|
Name: 'ShellMountPasswordNotification',
|
|
Extends: MessageTray.Notification,
|
|
|
|
_init: function(source, strings, icon, reaskPassword) {
|
|
this.parent(source, strings[0], null, { customContent: true, icon: icon });
|
|
|
|
// set the notification to transient and urgent, so that it
|
|
// expands out
|
|
this.setTransient(true);
|
|
this.setUrgency(MessageTray.Urgency.CRITICAL);
|
|
|
|
if (strings[1])
|
|
this.addBody(strings[1]);
|
|
|
|
if (reaskPassword) {
|
|
let label = new St.Label({ style_class: 'mount-password-reask',
|
|
text: _("Wrong password, please try again") });
|
|
|
|
this.addActor(label);
|
|
}
|
|
|
|
this._responseEntry = new St.Entry({ style_class: 'mount-password-entry',
|
|
can_focus: true });
|
|
this.setActionArea(this._responseEntry);
|
|
|
|
this._responseEntry.clutter_text.connect('activate',
|
|
Lang.bind(this, this._onEntryActivated));
|
|
this._responseEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
|
|
|
|
this._responseEntry.grab_key_focus();
|
|
},
|
|
|
|
_onEntryActivated: function() {
|
|
let text = this._responseEntry.get_text();
|
|
if (text == '')
|
|
return;
|
|
|
|
this.source.emit('password-ready', text);
|
|
}
|
|
});
|
|
|
|
const ShellProcessesDialog = new Lang.Class({
|
|
Name: 'ShellProcessesDialog',
|
|
Extends: ModalDialog.ModalDialog,
|
|
|
|
_init: function(icon) {
|
|
this.parent({ styleClass: 'show-processes-dialog' });
|
|
|
|
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_n_children() == 1)
|
|
scrollView.show();
|
|
}));
|
|
|
|
this._applicationList.connect('actor-removed',
|
|
Lang.bind(this, function() {
|
|
if (this._applicationList.get_n_children() == 0)
|
|
scrollView.hide();
|
|
}));
|
|
},
|
|
|
|
_setAppsForPids: function(pids) {
|
|
// remove all the items
|
|
this._applicationList.destroy_all_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('response', -1);
|
|
}));
|
|
}));
|
|
},
|
|
|
|
update: function(message, processes, choices) {
|
|
this._setAppsForPids(processes);
|
|
_setLabelsForMessage(this, message);
|
|
_setButtonsForChoices(this, choices);
|
|
}
|
|
});
|
|
Signals.addSignalMethods(ShellProcessesDialog.prototype);
|