endSessionDialog: Add logout/shutdown dialog

This commit adds a dialog for gnome-session to
privately use when initiating log outs and shut
downs.

Coordination is done over the bus.

https://bugzilla.gnome.org/show_bug.cgi?id=637187
This commit is contained in:
Ray Strode 2011-01-06 10:30:15 -05:00
parent 2905b0318d
commit e73e4375b8
5 changed files with 624 additions and 0 deletions

View File

@ -1165,6 +1165,62 @@ StTooltip StLabel {
background-color: rgba(0, 0, 0, 0.4);
}
/* End Session Dialog */
.end-session-dialog-subject {
font: 12pt sans-serif;
font-weight: bold;
color: #666666;
padding-top: 10px;
padding-left: 17px;
padding-bottom: 30px;
}
.end-session-dialog-description {
font: 10pt sans-serif;
color: white;
padding-left: 17px;
padding-right: 40px;
width: 16em;
}
.end-session-dialog-logout-icon {
border: 2px solid #8b8b8b;
border-radius: 5px;
width: 32px;
height: 32px;
}
.end-session-dialog-shutdown-icon {
width: 32px;
height: 32px;
}
.end-session-dialog-app-list {
font: 10pt sans-serif;
max-height: 200px;
padding-top: 42px;
padding-bottom: 42px;
padding-left: 17px;
padding-right: 32px;
}
.end-session-dialog-app-list-item {
padding-right: 1em;
}
.end-session-dialog-app-list-item-icon {
padding-right: 17px;
}
.end-session-dialog-app-list-item-name {
font: 10pt sans-serif;
}
.end-session-dialog-app-list-item-description {
font: 8pt sans-serif;
color: #444444;
}
/* Magnifier */
.magnifier-zoom-region {

View File

@ -21,6 +21,7 @@ nobase_dist_js_DATA = \
ui/dash.js \
ui/dnd.js \
ui/docDisplay.js \
ui/endSessionDialog.js \
ui/environment.js \
ui/extensionSystem.js \
ui/genericDisplay.js \

View File

@ -2,6 +2,7 @@
const DBus = imports.dbus;
const Lang = imports.lang;
const Signals = imports.signals;
const PresenceIface = {
name: 'org.gnome.SessionManager.Presence',
@ -43,3 +44,60 @@ Presence.prototype = {
}
};
DBus.proxifyPrototype(Presence.prototype, PresenceIface);
// Note inhibitors are immutable objects, so they don't
// change at runtime (changes always come in the form
// of new inhibitors)
const InhibitorIface = {
name: 'org.gnome.SessionManager.Inhibitor',
properties: [{ name: 'app_id',
signature: 's',
access: 'readonly' },
{ name: 'client_id',
signature: 's',
access: 'readonly' },
{ name: 'reason',
signature: 's',
access: 'readonly' },
{ name: 'flags',
signature: 'u',
access: 'readonly' },
{ name: 'toplevel_xid',
signature: 'u',
access: 'readonly' },
{ name: 'cookie',
signature: 'u',
access: 'readonly' }],
};
function Inhibitor(objectPath) {
this._init(objectPath);
}
Inhibitor.prototype = {
_init: function(objectPath) {
DBus.session.proxifyObject(this,
"org.gnome.SessionManager",
objectPath);
this.isLoaded = false;
this._loadingPropertiesCount = InhibitorIface.properties.length;
for (let i = 0; i < InhibitorIface.properties.length; i++) {
let propertyName = InhibitorIface.properties[i].name;
this.GetRemote(propertyName, Lang.bind(this,
function(value, exception) {
if (exception)
return;
this[propertyName] = value;
this._loadingPropertiesCount--;
if (this._loadingPropertiesCount == 0) {
this.isLoaded = true;
this.emit("is-loaded");
}
}));
}
},
};
DBus.proxifyPrototype(Inhibitor.prototype, InhibitorIface);
Signals.addSignalMethods(Inhibitor.prototype);

504
js/ui/endSessionDialog.js Normal file
View File

@ -0,0 +1,504 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
*
* Copyright 2010 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, 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.
*/
const DBus = imports.dbus;
const Lang = imports.lang;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Clutter = imports.gi.Clutter;
const Gdm = imports.gi.Gdm;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const GnomeSession = imports.misc.gnomeSession
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener;
let _endSessionDialog = null;
const _ITEM_ICON_SIZE = 48;
const _DIALOG_ICON_SIZE = 32;
const GSM_SESSION_MANAGER_LOGOUT_FORCE = 2;
const EndSessionDialogIface = {
name: 'org.gnome.SessionManager.EndSessionDialog',
methods: [{ name: 'Open',
inSignature: 'uuuao',
outSignature: ''
}
],
signals: [{ name: 'Canceled',
outSignature: '',
}],
properties: []
};
const logoutDialogContent = {
subjectWithUser: _("Log Out %s"),
subject: _("Log Out"),
inhibitedDescription: _("Click Log Out to quit these applications and log out of the system."),
uninhibitedDescriptionWithUser: _("%s will be logged out automatically in %d seconds."),
uninhibitedDescription: _("You will be logged out automatically in %d seconds."),
endDescription: _("Logging out of the system."),
confirmButtonText: _("Log Out"),
iconStyleClass: 'end-session-dialog-logout-icon'
};
const shutdownDialogContent = {
subject: _("Shut Down"),
inhibitedDescription: _("Click Shut Down to quit these applications and shut down the system."),
uninhibitedDescription: _("The system will shut down automatically in %d seconds."),
endDescription: _("Shutting down the system."),
confirmButtonText: _("Shut Down"),
iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon'
};
const restartDialogContent = {
subject: _("Restart"),
inhibitedDescription: _("Click Restart to quit these applications and restart the system."),
uninhibitedDescription: _("The system will restart automatically in %d seconds."),
endDescription: _("Restarting the system."),
confirmButtonText: _("Restart"),
iconName: 'system-shutdown',
iconStyleClass: 'end-session-dialog-shutdown-icon'
};
const DialogContent = {
0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent,
1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent,
2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent
};
function findAppFromInhibitor(inhibitor) {
let desktopFile = inhibitor.app_id;
if (!GLib.str_has_suffix(desktopFile, '.desktop'))
desktopFile += '.desktop';
let candidateDesktopFiles = [];
candidateDesktopFiles.push(desktopFile);
candidateDesktopFiles.push('gnome-' + desktopFile);
let appSystem = Shell.AppSystem.get_default();
let app = null;
for (let i = 0; i < candidateDesktopFiles.length; i++) {
try {
app = appSystem.get_app(candidateDesktopFiles[i]);
if (app)
break;
} catch(e) {
// ignore errors
}
}
return app;
}
function ListItem(app, reason) {
this._init(app, reason);
}
ListItem.prototype = {
_init: function(app, reason) {
this._app = app;
this._reason = reason;
if (this._reason == null)
this._reason = '';
let layout = new St.BoxLayout({ vertical: false});
this.actor = new St.Clickable({ style_class: 'end-session-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(_ITEM_ICON_SIZE);
let iconBin = new St.Bin({ style_class: 'end-session-dialog-app-list-item-icon',
child: this._icon });
layout.add(iconBin);
let textLayout = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item-text-box',
vertical: true });
layout.add(textLayout);
this._nameLabel = new St.Label({ text: this._app.get_name(),
style_class: 'end-session-dialog-app-list-item-name' });
textLayout.add(this._nameLabel,
{ expand: false,
x_fill: true });
this._descriptionLabel = new St.Label({ text: this._reason,
style_class: 'end-session-dialog-app-list-item-description' });
textLayout.add(this._descriptionLabel,
{ expand: true,
x_fill: true });
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
},
_onClicked: function() {
this.emit('activate');
this._app.activate();
}
};
Signals.addSignalMethods(ListItem.prototype);
// The logout timer only shows updates every 10 seconds
// until the last 10 seconds, then it shows updates every
// second. This function takes a given time and returns
// what we should show to the user for that time.
function _roundSecondsToInterval(totalSeconds, secondsLeft, interval) {
let time;
time = Math.ceil(secondsLeft);
// Final count down is in decrements of 1
if (time <= interval)
return time;
// Round up higher than last displayable time interval
time += interval - 1;
// Then round down to that time interval
if (time > totalSeconds)
time = Math.ceil(totalSeconds);
else
time -= time % interval;
return time;
}
function _setLabelText(label, text) {
if (text) {
label.set_text(text);
label.show();
} else {
label.set_text('');
label.hide();
}
}
function EndSessionDialog() {
if (_endSessionDialog == null) {
this._init();
DBus.session.exportObject('/org/gnome/SessionManager/EndSessionDialog',
this);
_endSessionDialog = this;
}
return _endSessionDialog;
}
function init() {
// This always returns the same singleton object
// By instantiating it initially, we register the
// bus object, etc.
let dialog = new EndSessionDialog();
}
EndSessionDialog.prototype = {
__proto__: ModalDialog.ModalDialog.prototype,
_init: function() {
ModalDialog.ModalDialog.prototype._init.call(this);
this._user = Gdm.UserManager.ref_default().get_user(GLib.get_user_name());
this._secondsLeft = 0;
this._totalSecondsToStayOpen = 0;
this._inhibitors = [];
this.connect('destroy',
Lang.bind(this, this._onDestroy));
this.connect('opened',
Lang.bind(this, this._onOpened));
this._userLoadedId = this._user.connect('notify::is_loaded',
Lang.bind(this, this._updateContent));
this._userChangedId = this._user.connect('changed',
Lang.bind(this, this._updateContent));
let mainContentLayout = new St.BoxLayout({ vertical: false });
this.contentLayout.add(mainContentLayout,
{ x_fill: true,
y_fill: false });
this._iconBin = new St.Bin();
mainContentLayout.add(this._iconBin,
{ x_fill: true,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.START });
let messageLayout = new St.BoxLayout({ vertical: true });
mainContentLayout.add(messageLayout,
{ y_align: St.Align.START });
this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' });
messageLayout.add(this._subjectLabel,
{ y_fill: false,
y_align: St.Align.START });
this._descriptionLabel = new St.Label({ style_class: 'end-session-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: 'end-session-dialog-app-list'});
scrollView.set_policy(Gtk.PolicyType.NEVER,
Gtk.PolicyType.AUTOMATIC);
this.contentLayout.add(scrollView,
{ x_fill: true,
y_fill: true });
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 });
},
_onDestroy: function() {
this._user.disconnect(this._userLoadedId);
this._user.disconnect(this._userChangedId);
},
_setIconFromFile: function(iconFile, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
this._iconBin.child = null;
if (iconFile) {
this._iconBin.show();
this._iconBin.set_style('background-image: url("' + iconFile + '");');
} else {
this._iconBin.hide();
}
},
_setIconFromName: function(iconName, styleClass) {
if (styleClass)
this._iconBin.set_style_class_name(styleClass);
this._iconBin.set_style(null);
if (iconName != null) {
let textureCache = St.TextureCache.get_default();
let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
iconName,
St.IconType.SYMBOLIC,
_DIALOG_ICON_SIZE);
this._iconBin.child = icon;
this._iconBin.show();
} else {
this._iconBin.child = null;
this._iconBin.hide();
}
},
_updateContent: function() {
if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED)
return;
let dialogContent = DialogContent[this._type];
let subject = dialogContent.subject;
let description;
if (this._user.is_loaded && !dialogContent.iconName) {
let iconFile = this._user.get_icon_file();
this._setIconFromFile(iconFile, dialogContent.iconStyleClass);
} else if (dialogContent.iconName) {
this._setIconFromName(dialogContent.iconName,
dialogContent.iconStyleClass);
}
if (this._inhibitors.length > 0) {
this._stopTimer();
description = dialogContent.inhibitedDescription;
} else if (this._secondsLeft > 0 && this._inhibitors.length == 0) {
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
this._secondsLeft,
10);
if (this._user.is_loaded) {
let realName = this._user.get_real_name();
if (realName != null) {
if (dialogContent.subjectWithUser)
subject = dialogContent.subjectWithUser.format(realName);
if (dialogContent.uninhibitedDescriptionWithUser)
description = dialogContent.uninhibitedDescriptionWithUser.format(realName, displayTime);
else
description = dialogContent.uninhibitedDescription.format(displayTime);
}
}
if (!description)
description = dialogContent.uninhibitedDescription.format(displayTime);
} else {
description = dialogContent.endDescription;
}
_setLabelText(this._subjectLabel, subject);
_setLabelText(this._descriptionLabel, description);
},
_updateButtons: function() {
if (this.state != ModalDialog.State.OPENING &&
this.state != ModalDialog.State.OPENED)
return;
let dialogContent = DialogContent[this._type];
let confirmButtonText = _("Confirm");
if (dialogContent.confirmButtonText)
confirmButtonText = dialogContent.confirmButtonText;
this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this.cancel),
key: Clutter.Escape
},
{ label: confirmButtonText,
action: Lang.bind(this, this._confirm)
}]);
},
cancel: function() {
this._stopTimer();
DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog',
'org.gnome.SessionManager.EndSessionDialog',
'Canceled', '', []);
this.close(global.get_current_time());
},
_confirm: function() {
this._fadeOutDialog();
this._stopTimer();
DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog',
'org.gnome.SessionManager.EndSessionDialog',
'Confirmed', '', []);
},
_onOpened: function() {
if (this._inhibitors.length == 0)
this._startTimer();
},
_startTimer: function() {
this._secondsLeft = this._totalSecondsToStayOpen;
Tweener.addTween(this,
{ _secondsLeft: 0,
time: this._secondsLeft,
transition: 'linear',
onUpdate: Lang.bind(this, this._updateContent),
onComplete: Lang.bind(this, this._confirm),
});
},
_stopTimer: function() {
Tweener.removeTweens(this);
this._secondsLeft = 0;
},
_onInhibitorLoaded: function(inhibitor) {
if (this._inhibitors.indexOf(inhibitor) < 0) {
// Stale inhibitor
return;
}
let app = findAppFromInhibitor(inhibitor);
if (app) {
let item = new ListItem(app, inhibitor.reason);
item.connect('activate',
Lang.bind(this, function() {
this.close(global.get_current_time());
}));
this._applicationList.add(item.actor, { x_fill: true });
this._stopTimer();
} else {
// inhibiting app is a service, not an application
this._inhibitors.splice(this._inhibitors.indexOf(inhibitor), 1);
}
this._updateContent();
},
OpenAsync: function(type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths, callback) {
this._totalSecondsToStayOpen = totalSecondsToStayOpen;
this._inhibitors = [];
this._applicationList.remove_all();
this._type = type;
if (!(this._type in DialogContent))
throw new DBus.DBusError('org.gnome.Shell.ModalDialog.TypeError',
"Unknown dialog type requested");
for (let i = 0; i < inhibitorObjectPaths.length; i++) {
let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i]);
inhibitor.connect('is-loaded',
Lang.bind(this, function() {
this._onInhibitorLoaded(inhibitor);
}));
this._inhibitors.push(inhibitor);
}
if (!this.open(timestamp))
throw new DBus.DBusError('org.gnome.Shell.ModalDialog.GrabError',
"Cannot grab pointer and keyboard");
this._updateButtons();
this._updateContent();
let signalId = this.connect('opened',
Lang.bind(this, function() {
callback();
this.disconnect(signalId);
}));
}
};
DBus.conformExport(EndSessionDialog.prototype, EndSessionDialogIface);

View File

@ -22,6 +22,7 @@ const _ = Gettext.gettext;
const Chrome = imports.ui.chrome;
const CtrlAltTab = imports.ui.ctrlAltTab;
const EndSessionDialog = imports.ui.endSessionDialog;
const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
@ -171,6 +172,10 @@ function start() {
}
});
// Provide the bus object for gnome-session to
// initiate logouts.
EndSessionDialog.init();
global.gdk_screen.connect('monitors-changed', _relayout);
ExtensionSystem.init();