[gnome-shell] endSessionDialog: Add logout/shutdown dialog
- From: Ray Strode <halfline src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] endSessionDialog: Add logout/shutdown dialog
- Date: Fri, 14 Jan 2011 05:13:15 +0000 (UTC)
commit e73e4375b8c4f1f0982ca0b23c24706e81c25bad
Author: Ray Strode <rstrode redhat com>
Date: Thu Jan 6 10:30:15 2011 -0500
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
data/theme/gnome-shell.css | 56 +++++
js/Makefile.am | 1 +
js/misc/gnomeSession.js | 58 +++++
js/ui/endSessionDialog.js | 504 ++++++++++++++++++++++++++++++++++++++++++++
js/ui/main.js | 5 +
5 files changed, 624 insertions(+), 0 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 2d83bc0..393cce9 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -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 {
diff --git a/js/Makefile.am b/js/Makefile.am
index 3dce4f2..1b02f49 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -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 \
diff --git a/js/misc/gnomeSession.js b/js/misc/gnomeSession.js
index 06f52a3..ef4ef84 100644
--- a/js/misc/gnomeSession.js
+++ b/js/misc/gnomeSession.js
@@ -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);
diff --git a/js/ui/endSessionDialog.js b/js/ui/endSessionDialog.js
new file mode 100644
index 0000000..62b69b1
--- /dev/null
+++ b/js/ui/endSessionDialog.js
@@ -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);
diff --git a/js/ui/main.js b/js/ui/main.js
index 1d068eb..1d456af 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -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();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]