/* -*- 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 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', inSignature: '', }], 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."), confirmButtons: [{ signal: 'ConfirmedLogout', label: _("Log Out") }], iconStyleClass: 'end-session-dialog-logout-icon' }; const shutdownDialogContent = { subject: _("Power Off"), inhibitedDescription: _("Click Power Off to quit these applications and power off the system."), uninhibitedDescription: _("The system will power off automatically in %d seconds."), endDescription: _("Powering off the system."), confirmButtons: [{ signal: 'ConfirmedReboot', label: _("Restart") }, { signal: 'ConfirmedShutdown', label: _("Power Off") }], 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."), confirmButtons: [{ signal: 'ConfirmedReboot', label: _("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.lookup_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.Button({ 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, { styleClass: 'end-session-dialog' }); 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 }); 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_children().length == 1) scrollView.show(); })); this._applicationList.connect('actor-removed', Lang.bind(this, function() { if (this._applicationList.get_children().length == 0) scrollView.hide(); })); }, _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(); if (GLib.file_test(iconFile, GLib.FileTest.EXISTS)) this._setIconFromFile(iconFile, dialogContent.iconStyleClass); else this._setIconFromName('avatar-default', 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() { let dialogContent = DialogContent[this._type]; let buttons = [{ action: Lang.bind(this, this.cancel), label: _("Cancel"), key: Clutter.Escape }]; for (let i = 0; i < dialogContent.confirmButtons.length; i++) { let signal = dialogContent.confirmButtons[i].signal; let label = dialogContent.confirmButtons[i].label; buttons.push({ action: Lang.bind(this, function() { this._confirm(signal); }), label: label }); } this.setButtons(buttons); }, close: function() { ModalDialog.ModalDialog.prototype.close.call(this); DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog', 'org.gnome.SessionManager.EndSessionDialog', 'Closed', '', []); }, 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(signal) { this._fadeOutDialog(); this._stopTimer(); DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog', 'org.gnome.SessionManager.EndSessionDialog', signal, '', []); }, _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, function() { let dialogContent = DialogContent[this._type]; let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1]; this._confirm(button.signal); }), }); }, _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.destroy_children(); 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); } this._updateButtons(); if (!this.open(timestamp)) throw new DBus.DBusError('org.gnome.Shell.ModalDialog.GrabError', "Cannot grab pointer and keyboard"); this._updateContent(); let signalId = this.connect('opened', Lang.bind(this, function() { callback(); this.disconnect(signalId); })); } }; DBus.conformExport(EndSessionDialog.prototype, EndSessionDialogIface);