diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 03c265735..c6e8c551d 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -95,64 +95,6 @@ function _fixMarkup(text, allowMarkup) { return GLib.markup_escape_text(text, -1); } -const FocusGrabber = new Lang.Class({ - Name: 'FocusGrabber', - - _init: function(actor) { - this._actor = actor; - this._prevKeyFocusActor = null; - this._focusActorChangedId = 0; - this._focused = false; - }, - - grabFocus: function() { - if (this._focused) - return; - - this._prevKeyFocusActor = global.stage.get_key_focus(); - - this._focusActorChangedId = global.stage.connect('notify::key-focus', Lang.bind(this, this._focusActorChanged)); - - if (!this._actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false)) - this._actor.grab_key_focus(); - - this._focused = true; - }, - - _focusUngrabbed: function() { - if (!this._focused) - return false; - - if (this._focusActorChangedId > 0) { - global.stage.disconnect(this._focusActorChangedId); - this._focusActorChangedId = 0; - } - - this._focused = false; - return true; - }, - - _focusActorChanged: function() { - let focusedActor = global.stage.get_key_focus(); - if (!focusedActor || !this._actor.contains(focusedActor)) - this._focusUngrabbed(); - }, - - ungrabFocus: function() { - if (!this._focusUngrabbed()) - return; - - if (this._prevKeyFocusActor) { - global.stage.set_key_focus(this._prevKeyFocusActor); - this._prevKeyFocusActor = null; - } else { - let focusedActor = global.stage.get_key_focus(); - if (focusedActor && this._actor.contains(focusedActor)) - global.stage.set_key_focus(null); - } - } -}); - const URLHighlighter = new Lang.Class({ Name: 'URLHighlighter', @@ -980,79 +922,9 @@ const MessageTray = new Lang.Class({ Name: 'MessageTray', _init: function() { - this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) { - this._onStatusChanged(proxy.status); - })); - this._busy = false; - this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) { - this._onStatusChanged(status); - })); - - this._notificationWidget = new St.Widget({ name: 'notification-container', - reactive: true, - track_hover: true, - y_align: Clutter.ActorAlign.START, - x_align: Clutter.ActorAlign.CENTER, - y_expand: true, - x_expand: true, - layout_manager: new Clutter.BinLayout() }); - this._notificationWidget.connect('key-release-event', Lang.bind(this, this._onNotificationKeyRelease)); - this._notificationWidget.connect('notify::hover', Lang.bind(this, this._onNotificationHoverChanged)); - this._notificationWidget.connect('notify::height', Lang.bind(this, function() { - this._notificationWidget.translation_y = -this._notificationWidget.height; - })); - - this._notificationBin = new St.Bin({ y_expand: true }); - this._notificationBin.set_y_align(Clutter.ActorAlign.START); - this._notificationWidget.add_actor(this._notificationBin); - this._notificationWidget.hide(); - this._notificationFocusGrabber = new FocusGrabber(this._notificationWidget); - this._notificationQueue = []; - this._notification = null; - this._notificationClickedId = 0; - - this._userActiveWhileNotificationShown = false; - - this.idleMonitor = Meta.IdleMonitor.get_core(); - - // pointerInNotification is sort of a misnomer -- it tracks whether - // a message tray notification should expand. The value is - // partially driven by the hover state of the notification, but has - // a lot of complex state related to timeouts and the current - // state of the pointer when a notification pops up. - this._pointerInNotification = false; - - // This tracks this._notificationWidget.hover and is used to fizzle - // out non-changing hover notifications in onNotificationHoverChanged. - this._notificationHovered = false; - - this._keyboardVisible = false; - this._notificationState = State.HIDDEN; - this._notificationTimeoutId = 0; - this._notificationRemoved = false; - this._reNotifyAfterHideNotification = null; - this._inCtrlAltTab = false; - - Main.layoutManager.trackChrome(this._notificationWidget); - - global.screen.connect('in-fullscreen-changed', Lang.bind(this, this._updateState)); - this._sources = new Map(); }, - close: function() { - this._escapeTray(); - }, - - _onNotificationKeyRelease: function(actor, event) { - if (event.get_key_symbol() == Clutter.KEY_Escape && event.get_state() == 0) { - this._expireNotification(); - return Clutter.EVENT_STOP; - } - - return Clutter.EVENT_PROPAGATE; - }, - _expireNotification: function() { this._notificationExpired = true; this._updateState(); @@ -1072,7 +944,7 @@ const MessageTray = new Lang.Class({ source.policy.store(); source.policy.connect('enable-changed', Lang.bind(this, this._onSourceEnableChanged, source)); - source.policy.connect('policy-changed', Lang.bind(this, this._updateState)); + // source.policy.connect('policy-changed', Lang.bind(this, this._updateState)); this._onSourceEnableChanged(source.policy, source); }, @@ -1130,402 +1002,8 @@ const MessageTray = new Lang.Class({ this._removeSource(source); }, - _onNotificationDestroy: function(notification) { - if (this._notification == notification && (this._notificationState == State.SHOWN || this._notificationState == State.SHOWING)) { - this._updateNotificationTimeout(0); - this._notificationRemoved = true; - this._updateState(); - return; - } - - let index = this._notificationQueue.indexOf(notification); - if (index != -1) - this._notificationQueue.splice(index, 1); - }, - - openTray: function() { - if (Main.overview.animationInProgress) - return; - - this._traySummoned = true; - this._updateState(); - }, - - hide: function() { - this._traySummoned = false; - this._updateState(); - }, - _onNotify: function(source, notification) { - if (this._notification == notification) { - // If a notification that is being shown is updated, we update - // how it is shown and extend the time until it auto-hides. - // If a new notification is updated while it is being hidden, - // we stop hiding it and show it again. - this._updateShowingNotification(); - } else if (this._notificationQueue.indexOf(notification) < 0) { - notification.connect('destroy', - Lang.bind(this, this._onNotificationDestroy)); - this._notificationQueue.push(notification); - this._notificationQueue.sort(function(notification1, notification2) { - return (notification2.urgency - notification1.urgency); - }); - } - this._updateState(); - }, - - _resetNotificationLeftTimeout: function() { - this._useLongerNotificationLeftTimeout = false; - if (this._notificationLeftTimeoutId) { - Mainloop.source_remove(this._notificationLeftTimeoutId); - this._notificationLeftTimeoutId = 0; - this._notificationLeftMouseX = -1; - this._notificationLeftMouseY = -1; - } - }, - - _onNotificationHoverChanged: function() { - if (this._notificationWidget.hover == this._notificationHovered) - return; - - this._notificationHovered = this._notificationWidget.hover; - if (this._notificationHovered) { - // No dwell inside notifications at the bottom of the screen - // this._cancelTrayDwell(); - - this._resetNotificationLeftTimeout(); - - if (this._showNotificationMouseX >= 0) { - let actorAtShowNotificationPosition = - global.stage.get_actor_at_pos(Clutter.PickMode.ALL, this._showNotificationMouseX, this._showNotificationMouseY); - this._showNotificationMouseX = -1; - this._showNotificationMouseY = -1; - // Don't set this._pointerInNotification to true if the pointer was initially in the area where the notification - // popped up. That way we will not be expanding notifications that happen to pop up over the pointer - // automatically. Instead, the user is able to expand the notification by mousing away from it and then - // mousing back in. Because this is an expected action, we set the boolean flag that indicates that a longer - // timeout should be used before popping down the notification. - /* - if (this.actor.contains(actorAtShowNotificationPosition)) { - this._useLongerNotificationLeftTimeout = true; - return; - } - */ - } - - this._pointerInNotification = true; - this._updateState(); - } else { - // We record the position of the mouse the moment it leaves the tray. These coordinates are used in - // this._onNotificationLeftTimeout() to determine if the mouse has moved far enough during the initial timeout for us - // to consider that the user intended to leave the tray and therefore hide the tray. If the mouse is still - // close to its previous position, we extend the timeout once. - let [x, y, mods] = global.get_pointer(); - this._notificationLeftMouseX = x; - this._notificationLeftMouseY = y; - - // We wait just a little before hiding the message tray in case the user quickly moves the mouse back into it. - // We wait for a longer period if the notification popped up where the mouse pointer was already positioned. - // That gives the user more time to mouse away from the notification and mouse back in in order to expand it. - let timeout = this._useLongerNotificationLeftTimeout ? LONGER_HIDE_TIMEOUT * 1000 : HIDE_TIMEOUT * 1000; - this._notificationLeftTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, this._onNotificationLeftTimeout)); - GLib.Source.set_name_by_id(this._notificationLeftTimeoutId, '[gnome-shell] this._onNotificationLeftTimeout'); - } - }, - - _onKeyboardVisibleChanged: function(layoutManager, keyboardVisible) { - this._keyboardVisible = keyboardVisible; - this._updateState(); - }, - - _onStatusChanged: function(status) { - if (status == GnomeSession.PresenceStatus.BUSY) { - // remove notification and allow the summary to be closed now - this._updateNotificationTimeout(0); - this._busy = true; - } else if (status != GnomeSession.PresenceStatus.IDLE) { - // We preserve the previous value of this._busy if the status turns to IDLE - // so that we don't start showing notifications queued during the BUSY state - // as the screensaver gets activated. - this._busy = false; - } - - this._updateState(); - }, - - _onNotificationLeftTimeout: function() { - let [x, y, mods] = global.get_pointer(); - // We extend the timeout once if the mouse moved no further than MOUSE_LEFT_ACTOR_THRESHOLD to either side or up. - // We don't check how far down the mouse moved because any point above the tray, but below the exit coordinate, - // is close to the tray. - if (this._notificationLeftMouseX > -1 && - y > this._notificationLeftMouseY - MOUSE_LEFT_ACTOR_THRESHOLD && - x < this._notificationLeftMouseX + MOUSE_LEFT_ACTOR_THRESHOLD && - x > this._notificationLeftMouseX - MOUSE_LEFT_ACTOR_THRESHOLD) { - this._notificationLeftMouseX = -1; - this._notificationLeftTimeoutId = Mainloop.timeout_add(LONGER_HIDE_TIMEOUT * 1000, - Lang.bind(this, this._onNotificationLeftTimeout)); - GLib.Source.set_name_by_id(this._notificationLeftTimeoutId, '[gnome-shell] this._onNotificationLeftTimeout'); - } else { - this._notificationLeftTimeoutId = 0; - this._useLongerNotificationLeftTimeout = false; - this._pointerInNotification = false; - this._updateNotificationTimeout(0); - this._updateState(); - } - return GLib.SOURCE_REMOVE; - }, - - _escapeTray: function() { - this._pointerInNotification = false; - this._traySummoned = false; - this._updateNotificationTimeout(0); - this._updateState(); - }, - - // All of the logic for what happens when occurs here; the various - // event handlers merely update variables such as - // 'this._pointerInNotification', 'this._traySummoned', etc, and - // _updateState() figures out what (if anything) needs to be done - // at the present time. - _updateState: function() { - // If our state changes caused _updateState to be called, - // just exit now to prevent reentrancy issues. - if (this._updatingState) - return; - - this._updatingState = true; - - // Filter out acknowledged notifications. - this._notificationQueue = this._notificationQueue.filter(function(n) { - return !n.acknowledged; - }); - - let hasNotifications = Main.sessionMode.hasNotifications; - - if (this._notificationState == State.HIDDEN) { - let shouldShowNotification = (hasNotifications && !this._traySummoned); - let nextNotification = this._notificationQueue[0] || null; - if (shouldShowNotification && nextNotification) { - let limited = this._busy || Main.layoutManager.bottomMonitor.inFullscreen; - let showNextNotification = (!limited || nextNotification.forFeedback || nextNotification.urgency == Urgency.CRITICAL); - if (showNextNotification) - this._showNotification(); - } - } else if (this._notificationState == State.SHOWN) { - let expired = (this._userActiveWhileNotificationShown && - this._notificationTimeoutId == 0 && - this._notification.urgency != Urgency.CRITICAL && - !this._notification.focused && - !this._pointerInNotification) || this._notificationExpired; - let mustClose = (this._notificationRemoved || !hasNotifications || expired || this._traySummoned); - - if (mustClose) { - let animate = hasNotifications && !this._notificationRemoved; - this._hideNotification(animate); - } else if (this._pointerInNotification) { - this._ensureNotificationFocused(); - } - } - - this._updatingState = false; - - // Clean transient variables that are used to communicate actions - // to updateState() - this._notificationExpired = false; - }, - - _tween: function(actor, statevar, value, params) { - let onComplete = params.onComplete; - let onCompleteScope = params.onCompleteScope; - let onCompleteParams = params.onCompleteParams; - - params.onComplete = this._tweenComplete; - params.onCompleteScope = this; - params.onCompleteParams = [statevar, value, onComplete, onCompleteScope, onCompleteParams]; - - // Remove other tweens that could mess with the state machine - Tweener.removeTweens(actor); - Tweener.addTween(actor, params); - - let valuing = (value == State.SHOWN) ? State.SHOWING : State.HIDING; - this[statevar] = valuing; - }, - - _tweenComplete: function(statevar, value, onComplete, onCompleteScope, onCompleteParams) { - this[statevar] = value; - if (onComplete) - onComplete.apply(onCompleteScope, onCompleteParams); - this._updateState(); - }, - - _showTray: function() { - /* - if (!this._grabHelper.grab({ actor: this.actor, - onUngrab: Lang.bind(this, this._escapeTray) })) { - this._traySummoned = false; - return false; - } - - this.emit('showing'); - this._tween(this.actor, '_trayState', State.SHOWN, - { y: -this.actor.height, - time: ANIMATION_TIME, - transition: 'easeOutQuad' - }); - - */ - return true; - }, - - _onIdleMonitorBecameActive: function() { - this._userActiveWhileNotificationShown = true; - this._updateNotificationTimeout(2000); - this._updateState(); - }, - - _showNotification: function() { - this._notification = this._notificationQueue.shift(); - - this._userActiveWhileNotificationShown = this.idleMonitor.get_idletime() <= IDLE_TIME; - if (!this._userActiveWhileNotificationShown) { - // If the user isn't active, set up a watch to let us know - // when the user becomes active. - this.idleMonitor.add_user_active_watch(Lang.bind(this, this._onIdleMonitorBecameActive)); - } - - this._notificationClickedId = this._notification.connect('done-displaying', - Lang.bind(this, this._escapeTray)); - this._notificationUnfocusedId = this._notification.connect('unfocused', Lang.bind(this, function() { - this._updateState(); - })); - this._notificationBin.child = this._notification.actor; - - this._notificationWidget.opacity = 0; - this._notificationWidget.show(); - - this._updateShowingNotification(); - - let [x, y, mods] = global.get_pointer(); - // We save the position of the mouse at the time when we started showing the notification - // in order to determine if the notification popped up under it. We make that check if - // the user starts moving the mouse and _onNotificationHoverChanged() gets called. We don't - // expand the notification if it just happened to pop up under the mouse unless the user - // explicitly mouses away from it and then mouses back in. - this._showNotificationMouseX = x; - this._showNotificationMouseY = y; - // We save the coordinates of the mouse at the time when we started showing the notification - // and then we update it in _notificationTimeout(). We don't pop down the notification if - // the mouse is moving towards it or within it. - this._lastSeenMouseX = x; - this._lastSeenMouseY = y; - - this._resetNotificationLeftTimeout(); - }, - - _updateShowingNotification: function() { - this._notification.acknowledged = true; - this._notification.playSound(); - - // We tween all notifications to full opacity. This ensures that both new notifications and - // notifications that might have been in the process of hiding get full opacity. - // - // We use this._showNotificationCompleted() onComplete callback to extend the time the updated - // notification is being shown. - - this._tween(this._notificationWidget, '_notificationState', State.SHOWN, - { opacity: 255, - time: ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: this._showNotificationCompleted, - onCompleteScope: this - }); - }, - - _showNotificationCompleted: function() { - if (this._notification.urgency != Urgency.CRITICAL) - this._updateNotificationTimeout(NOTIFICATION_TIMEOUT * 1000); - }, - - _updateNotificationTimeout: function(timeout) { - if (this._notificationTimeoutId) { - Mainloop.source_remove(this._notificationTimeoutId); - this._notificationTimeoutId = 0; - } - if (timeout > 0) { - this._notificationTimeoutId = - Mainloop.timeout_add(timeout, - Lang.bind(this, this._notificationTimeout)); - GLib.Source.set_name_by_id(this._notificationTimeoutId, '[gnome-shell] this._notificationTimeout'); - } - }, - - _notificationTimeout: function() { - let [x, y, mods] = global.get_pointer(); - if (y > this._lastSeenMouseY + 10 && !this._notificationHovered) { - // The mouse is moving towards the notification, so don't - // hide it yet. (We just create a new timeout (and destroy - // the old one) each time because the bookkeeping is - // simpler.) - this._updateNotificationTimeout(1000); - } else if (this._useLongerNotificationLeftTimeout && !this._notificationLeftTimeoutId && - (x != this._lastSeenMouseX || y != this._lastSeenMouseY)) { - // Refresh the timeout if the notification originally - // popped up under the pointer, and the pointer is hovering - // inside it. - this._updateNotificationTimeout(1000); - } else { - this._notificationTimeoutId = 0; - this._updateState(); - } - - this._lastSeenMouseX = x; - this._lastSeenMouseY = y; - return GLib.SOURCE_REMOVE; - }, - - _hideNotification: function(animate) { - this._notificationFocusGrabber.ungrabFocus(); - - if (this._notificationClickedId) { - this._notification.disconnect(this._notificationClickedId); - this._notificationClickedId = 0; - } - if (this._notificationUnfocusedId) { - this._notification.disconnect(this._notificationUnfocusedId); - this._notificationUnfocusedId = 0; - } - - this._resetNotificationLeftTimeout(); - - if (animate) { - this._tween(this._notificationWidget, '_notificationState', State.HIDDEN, - { opacity: 0, - time: ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: this._hideNotificationCompleted, - onCompleteScope: this - }); - } else { - Tweener.removeTweens(this._notificationWidget); - this._notificationWidget.opacity = 0; - this._notificationState = State.HIDDEN; - this._hideNotificationCompleted(); - } - }, - - _hideNotificationCompleted: function() { - let notification = this._notification; - this._notification = null; - - this._pointerInNotification = false; - this._notificationRemoved = false; - this._notificationBin.child = null; - this._notificationWidget.hide(); - }, - - _ensureNotificationFocused: function() { - this._notificationFocusGrabber.grabFocus(); + // Fill in here. }, }); Signals.addSignalMethods(MessageTray.prototype); diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js index b35be4366..c6ae1bd14 100644 --- a/js/ui/notificationDaemon.js +++ b/js/ui/notificationDaemon.js @@ -114,7 +114,7 @@ const FdoNotificationDaemon = new Lang.Class({ Main.overview.connect('hidden', Lang.bind(this, this._onFocusAppChanged)); - this._trayManager.manage_screen(global.screen, Main.messageTray.actor); + // this._trayManager.manage_screen(global.screen, Main.messageTray.actor); }, _imageForNotificationData: function(hints) {