// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const Cairo = imports.cairo; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GnomeDesktop = imports.gi.GnomeDesktop; const Lang = imports.lang; const Mainloop = imports.mainloop; const Meta = imports.gi.Meta; const Signals = imports.signals; const St = imports.gi.St; const TweenerEquations = imports.tweener.equations; const GnomeSession = imports.misc.gnomeSession; const Layout = imports.ui.layout; const LoginManager = imports.misc.loginManager; const Lightbox = imports.ui.lightbox; const Main = imports.ui.main; const Overview = imports.ui.overview; const MessageTray = imports.ui.messageTray; const ShellDBus = imports.ui.shellDBus; const Tweener = imports.ui.tweener; const Util = imports.misc.util; const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver'; const LOCK_ENABLED_KEY = 'lock-enabled'; const LOCK_DELAY_KEY = 'lock-delay'; const CURTAIN_SLIDE_TIME = 0.3; // fraction of screen height the arrow must reach before completing // the slide up automatically const ARROW_DRAG_THRESHOLD = 0.1; // Parameters for the arrow animation const N_ARROWS = 3; const ARROW_ANIMATION_TIME = 0.6; const ARROW_ANIMATION_PEAK_OPACITY = 0.4; // The distance in px that the lock screen will move to when pressing // a key that has no effect in the lock screen (bumping it) const BUMP_SIZE = 25; const BUMP_TIME = 0.3; const SUMMARY_ICON_SIZE = 48; // Lightbox fading times // STANDARD_FADE_TIME is used when the session goes idle, while // SHORT_FADE_TIME is used when requesting lock explicitly from the user menu const STANDARD_FADE_TIME = 10; const SHORT_FADE_TIME = 0.3; function sample(offx, offy) { return 'texel += texture2D (sampler, tex_coord.st + pixel_step * ' + 'vec2 (' + offx + ',' + offy + '));\n' } const GLSL_BLUR_EFFECT_DECLARATIONS = ' \ uniform vec2 pixel_step;\n \ uniform float desaturation;\n \ vec4 apply_blur(in sampler2D sampler, in vec2 tex_coord) {\n \ vec4 texel;\n \ texel = texture2D (sampler, tex_coord.st);\n' + sample(-1.0, -1.0) + sample( 0.0, -1.0) + sample(+1.0, -1.0) + sample(-1.0, 0.0) + sample(+1.0, 0.0) + sample(-1.0, +1.0) + sample( 0.0, +1.0) + sample(+1.0, +1.0) + ' \ texel /= 9.0;\n \ return texel;\n \ }\n \ vec3 desaturate (const vec3 color)\n \ {\n \ const vec3 gray_conv = vec3 (0.299, 0.587, 0.114);\n \ vec3 gray = vec3 (dot (gray_conv, color));\n \ return vec3 (mix (color.rgb, gray, desaturation));\n \ }'; const GLSL_BLUR_EFFECT_CODE = ' \ cogl_texel = apply_blur(cogl_sampler, cogl_tex_coord.st);\n \ cogl_texel.rgb = desaturate(cogl_texel.rgb);\n'; const Clock = new Lang.Class({ Name: 'ScreenShieldClock', CLOCK_FORMAT_KEY: 'clock-format', CLOCK_SHOW_SECONDS_KEY: 'clock-show-seconds', _init: function() { this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock', vertical: true }); this._time = new St.Label({ style_class: 'screen-shield-clock-time' }); this._date = new St.Label({ style_class: 'screen-shield-clock-date' }); this.actor.add(this._time, { x_align: St.Align.MIDDLE }); this.actor.add(this._date, { x_align: St.Align.MIDDLE }); this._wallClock = new GnomeDesktop.WallClock({ time_only: true }); this._wallClock.connect('notify::clock', Lang.bind(this, this._updateClock)); this._updateClock(); }, _updateClock: function() { this._time.text = this._wallClock.clock; let date = new Date(); /* Translators: This is a time format for a date in long format */ this._date.text = date.toLocaleFormat(_("%A, %B %d")); }, destroy: function() { this.actor.destroy(); this._wallClock.run_dispose(); } }); const NotificationsBox = new Lang.Class({ Name: 'NotificationsBox', _init: function() { this.actor = new St.BoxLayout({ vertical: true, name: 'screenShieldNotifications', style_class: 'screen-shield-notifications-box' }); this._residentNotificationBox = new St.BoxLayout({ vertical: true, style_class: 'screen-shield-notifications-box' }); let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START }); this._persistentNotificationBox = new St.BoxLayout({ vertical: true, style_class: 'screen-shield-notifications-box' }); scrollView.add_actor(this._persistentNotificationBox); this.actor.add(this._residentNotificationBox, { x_fill: true }); this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START }); this._items = []; Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) { this._summaryItemAdded(Main.messageTray, item, true); })); this._updateVisibility(); this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded)); }, destroy: function() { if (this._summaryAddedId) { Main.messageTray.disconnect(this._summaryAddedId); this._summaryAddedId = 0; } for (let i = 0; i < this._items.length; i++) this._removeItem(this._items[i]); this._items = []; this.actor.destroy(); }, _updateVisibility: function() { this._residentNotificationBox.visible = this._residentNotificationBox.get_n_children() > 0; this._persistentNotificationBox.visible = this._persistentNotificationBox.get_children().some(function(a) { return a.visible; }); this.actor.visible = this._residentNotificationBox.visible || this._persistentNotificationBox.visible; }, _sourceIsResident: function(source) { return source.hasResidentNotification() && !source.isChat; }, _makeNotificationCountText: function(count, isChat) { if (isChat) return ngettext("%d new message", "%d new messages", count).format(count); else return ngettext("%d new notification", "%d new notifications", count).format(count); }, _makeNotificationSource: function(source) { let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' }); let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE); box.add(sourceActor.actor, { y_fill: true }); let textBox = new St.BoxLayout({ vertical: true }); box.add(textBox, { y_fill: false, y_align: St.Align.START }); let label = new St.Label({ text: source.title, style_class: 'screen-shield-notification-label' }); textBox.add(label); let count = source.unseenCount; let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat), style_class: 'screen-shield-notification-count-text' }); textBox.add(countLabel); box.visible = count != 0; return [box, countLabel]; }, _summaryItemAdded: function(tray, item, dontUpdateVisibility) { // Ignore transient sources, or sources explicitly marked not to show // in the lock screen if (item.source.isTransient || !item.source.showInLockScreen) return; let obj = { item: item, source: item.source, resident: this._sourceIsResident(item.source), contentUpdatedId: 0, sourceDestroyId: 0, sourceBox: null, countLabel: null, }; if (obj.resident) { this._residentNotificationBox.add(item.notificationStackWidget); item.closeButton.hide(); item.prepareNotificationStackForShowing(); } else { [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source); this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START }); } obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated)); obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged)); obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged)); obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy)); this._items.push(obj); if (!dontUpdateVisibility) this._updateVisibility(); }, _findSource: function(source) { for (let i = 0; i < this._items.length; i++) { if (this._items[i].source == source) return i; } return -1; }, _onItemContentUpdated: function(item) { let obj = this._items[this._findSource(item.source)]; this._updateItem(obj); }, _onSourceChanged: function(source) { let obj = this._items[this._findSource(source)]; this._updateItem(obj); }, _updateItem: function(obj) { let itemShouldBeResident = this._sourceIsResident(obj.source); if (itemShouldBeResident && obj.resident) { // Nothing to do here, the actor is already updated return; } if (obj.resident && !itemShouldBeResident) { // make into a regular item obj.item.doneShowingNotificationStack(); this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget); [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source); this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START }); } else if (itemShouldBeResident && !obj.resident) { // make into a resident item obj.sourceBox.destroy(); obj.sourceBox = obj.countLabel = null; obj.resident = true; this._residentNotificationBox.add(obj.item.notificationStackWidget); obj.item.closeButton.hide(); obj.item.prepareNotificationStackForShowing(); } else { // just update the counter let count = obj.source.unseenCount; obj.countLabel.text = this._makeNotificationCountText(count, obj.source.isChat); obj.sourceBox.visible = count != 0; } this._updateVisibility(); }, _onSourceDestroy: function(source) { let idx = this._findSource(source); this._removeItem(this._items[idx]); this._items.splice(idx, 1); this._updateVisibility(); }, _removeItem: function(obj) { if (obj.resident) { obj.item.doneShowingNotificationStack(); this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget); } else { obj.sourceBox.destroy(); } obj.item.disconnect(obj.contentUpdatedId); obj.source.disconnect(obj.sourceDestroyId); obj.source.disconnect(obj.sourceCountChangedId); obj.source.disconnect(obj.sourceTitleChangedId); }, }); const Arrow = new Lang.Class({ Name: 'Arrow', Extends: St.Bin, _init: function(params) { this.parent(params); this.x_fill = this.y_fill = true; this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); this._drawingArea = new St.DrawingArea(); this._drawingArea.connect('repaint', Lang.bind(this, this._drawArrow)); this.child = this._drawingArea; this._shadowHelper = null; this._shadowWidth = this._shadowHeight = 0; }, _drawArrow: function(arrow) { let cr = arrow.get_context(); let [w, h] = arrow.get_surface_size(); let node = this.get_theme_node(); let thickness = node.get_length('-arrow-thickness'); Clutter.cairo_set_source_color(cr, node.get_foreground_color()); cr.setLineCap(Cairo.LineCap.ROUND); cr.setLineWidth(thickness); cr.moveTo(thickness / 2, h - thickness / 2); cr.lineTo(w/2, thickness); cr.lineTo(w - thickness / 2, h - thickness / 2); cr.stroke(); }, vfunc_style_changed: function() { let node = this.get_theme_node(); this._shadow = node.get_shadow('-arrow-shadow'); if (this._shadow) this._shadowHelper = St.ShadowHelper.new(this._shadow); else this._shadowHelper = null; }, vfunc_paint: function() { if (this._shadowHelper) { this._shadowHelper.update(this._drawingArea); let allocation = this._drawingArea.get_allocation_box(); let paintOpacity = this._drawingArea.get_paint_opacity(); this._shadowHelper.paint(allocation, paintOpacity); } this._drawingArea.paint(); } }); function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } /** * To test screen shield, make sure to kill gnome-screensaver. * * If you are setting org.gnome.desktop.session.idle-delay directly in dconf, * rather than through System Settings, you also need to set * org.gnome.settings-daemon.plugins.power.sleep-display-ac and * org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value. * This will ensure that the screen blanks at the right time when it fades out. * https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependance. */ const ScreenShield = new Lang.Class({ Name: 'ScreenShield', _init: function() { this.actor = Main.layoutManager.screenShieldGroup; this._lockScreenState = MessageTray.State.HIDDEN; this._lockScreenGroup = new St.Widget({ x_expand: true, y_expand: true, reactive: true, can_focus: true, name: 'lockScreenGroup', }); this._lockScreenGroup.connect('key-release-event', Lang.bind(this, this._onLockScreenKeyRelease)); this._lockScreenGroup.connect('scroll-event', Lang.bind(this, this._onLockScreenScroll)); Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic'); this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(), name: 'lockScreenContents' }); this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true })); let backgroundActor = Meta.BackgroundActor.new_for_screen(global.screen); backgroundActor.add_glsl_snippet(Meta.SnippetHook.TEXTURE_LOOKUP, GLSL_BLUR_EFFECT_DECLARATIONS, GLSL_BLUR_EFFECT_CODE, true); backgroundActor.set_uniform_float('desaturation', 1, 1, [0.6]); backgroundActor.connect('notify::size', function(actor) { actor.set_uniform_float('pixel_step', 2, 1, [1/actor.width, 1/actor.height]); }); this._background = new St.Bin({ style_class: 'screen-shield-background', child: backgroundActor }); this._lockScreenGroup.add_actor(this._background); this._lockScreenGroup.add_actor(this._lockScreenContents); this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows', vertical: true, x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.END, // HACK: without these, ClutterBinLayout // ignores alignment properties on the actor x_expand: true, y_expand: true }); for (let i = 0; i < N_ARROWS; i++) { let arrow = new Arrow({ opacity: 0 }); this._arrowContainer.add_actor(arrow); } this._lockScreenContents.add_actor(this._arrowContainer); this._dragAction = new Clutter.GestureAction(); this._dragAction.connect('gesture-begin', Lang.bind(this, this._onDragBegin)); this._dragAction.connect('gesture-progress', Lang.bind(this, this._onDragMotion)); this._dragAction.connect('gesture-end', Lang.bind(this, this._onDragEnd)); this._lockScreenGroup.add_action(this._dragAction); this._lockDialogGroup = new St.Widget({ x_expand: true, y_expand: true, pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }), name: 'lockDialogGroup' }); this.actor.add_actor(this._lockDialogGroup); this.actor.add_actor(this._lockScreenGroup); this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) { if (error) { logError(error, 'Error while reading gnome-session presence'); return; } this._onStatusChanged(proxy.status); })); this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) { this._onStatusChanged(status); })); this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this); this._loginManager = LoginManager.getLoginManager(); this._loginSession = this._loginManager.getCurrentSessionProxy(); this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); })); this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.unlock(); })); this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA }); this._isModal = false; this._hasLockScreen = false; this._isGreeter = false; this._isActive = false; this._inUnlockAnimation = false; this._activationTime = 0; this._lightbox = new Lightbox.Lightbox(Main.uiGroup, { inhibitEvents: true, fadeInTime: STANDARD_FADE_TIME, fadeFactor: 1 }); this.idleMonitor = new GnomeDesktop.IdleMonitor(); }, _onLockScreenKeyRelease: function(actor, event) { let symbol = event.get_key_symbol(); // Do nothing if the lock screen is not fully shown. // This avoids reusing the previous (and stale) unlock // dialog if esc is pressed while the curtain is going // down after cancel. // Similarly, don't bump if the lock screen is not showing or is // animating, as the bump overrides the animation and would // remove any onComplete handler. if (this._lockScreenState != MessageTray.State.SHOWN) return false; if (symbol == Clutter.KEY_Escape || symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter) { this._ensureUnlockDialog(true, true); this._hideLockScreen(true, 0); return true; } this._bumpLockScreen(); return true; }, _onLockScreenScroll: function(actor, event) { if (this._lockScreenState != MessageTray.State.SHOWN) return false; let delta = 0; if (event.get_scroll_direction() == Clutter.ScrollDirection.UP) delta = 5; else if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH) delta = Math.max(0, event.get_scroll_delta()[0]); this._lockScreenScrollCounter += delta; // 7 standard scrolls to lift up if (this._lockScreenScrollCounter > 35) { this._ensureUnlockDialog(false, true); this._hideLockScreen(true, 0); } return true; }, _animateArrows: function() { let arrows = this._arrowContainer.get_children(); let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1); let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY; for (let i = 0; i < arrows.length; i++) { arrows.opacity = 0; Tweener.addTween(arrows[i], { opacity: 0, delay: unitaryDelay * (N_ARROWS - (i + 1)), time: ARROW_ANIMATION_TIME, transition: function(t, b, c, d) { if (t < d/2) return TweenerEquations.easeOutQuad(t, 0, maxOpacity, d/2); else return TweenerEquations.easeInQuad(t - d/2, maxOpacity, -maxOpacity, d/2); } }); } return true; }, _onDragBegin: function() { Tweener.removeTweens(this._lockScreenGroup); this._lockScreenState = MessageTray.State.HIDING; this._ensureUnlockDialog(false, false); return true; }, _onDragMotion: function() { let [origX, origY] = this._dragAction.get_press_coords(0); let [currentX, currentY] = this._dragAction.get_motion_coords(0); let newY = currentY - origY; newY = clamp(newY, -global.stage.height, 0); this._lockScreenGroup.y = newY; return true; }, _onDragEnd: function(action, actor, eventX, eventY, modifiers) { if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) { // Complete motion automatically let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0); this._hideLockScreen(true, -velocityY); this._ensureUnlockDialog(false, true); } else { // restore the lock screen to its original place // try to use the same speed as the normal animation let h = global.stage.height; let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h; Tweener.removeTweens(this._lockScreenGroup); Tweener.addTween(this._lockScreenGroup, { y: 0, time: time, transition: 'easeInQuad', onComplete: function() { this._lockScreenGroup.fixed_position_set = false; this._lockScreenState = MessageTray.State.SHOWN; }, onCompleteScope: this, }); // If we have a unlock dialog, cancel it if (this._dialog) { this._dialog.cancel(); if (!this._isGreeter) { this._dialog = null; } } } }, _onStatusChanged: function(status) { if (status != GnomeSession.PresenceStatus.IDLE) return; if (this._dialog) { this._dialog.cancel(); if (!this._isGreeter) { this._dialog = null; } } if (!this._isModal) { Main.pushModal(this.actor, { keybindingMode: Main.KeybindingMode.LOCK_SCREEN }); this._isModal = true; } if (!this._isActive) { this._lightbox.show(); if (this._activationTime == 0) this._activationTime = GLib.get_monotonic_time(); this._becameActiveId = this.idleMonitor.connect('became-active', Lang.bind(this, function() { this.idleMonitor.disconnect(this._becameActiveId); let lightboxWasShown = this._lightbox.shown; this._lightbox.hide(); // GLib.get_monotonic_time() returns microseconds, convert to seconds let elapsedTime = (GLib.get_monotonic_time() - this._activationTime) / 1000000; let shouldLock = lightboxWasShown && this._settings.get_boolean(LOCK_ENABLED_KEY) && (elapsedTime >= this._settings.get_uint(LOCK_DELAY_KEY)); if (shouldLock || this._isLocked) { this.lock(false); } else if (this._isActive) { this.unlock(); } })); this._isActive = true; this.emit('lock-status-changed'); } }, showDialog: function() { // Ensure that the stage window is mapped, before taking a grab // otherwise X errors out Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { if (!this._isModal) { Main.pushModal(this.actor); this._isModal = true; } return false; })); this.actor.show(); this._isGreeter = Main.sessionMode.isGreeter; this._ensureUnlockDialog(true, true); this._hideLockScreen(false, 0); }, _bumpLockScreen: function() { Tweener.removeTweens(this._lockScreenGroup); Tweener.addTween(this._lockScreenGroup, { y: -BUMP_SIZE, time: BUMP_TIME / 2, transition: 'easeOutQuad', onComplete: function() { Tweener.addTween(this, { y: 0, time: BUMP_TIME / 2, transition: 'easeInQuad' }); } }); }, _hideLockScreen: function(animate, velocity) { this._lockScreenState = MessageTray.State.HIDING; if (animate) { // Tween the lock screen out of screen // if velocity is not specified (i.e. we come here from pressing ESC), // use the same speed regardless of original position // if velocity is specified, it's in pixels per milliseconds let h = global.stage.height; let delta = (h + this._lockScreenGroup.y); let min_velocity = global.stage.height / (CURTAIN_SLIDE_TIME * 1000); velocity = Math.max(min_velocity, velocity); let time = (delta / velocity) / 1000; Tweener.removeTweens(this._lockScreenGroup); Tweener.addTween(this._lockScreenGroup, { y: -h, time: time, transition: 'easeInQuad', onComplete: function() { this._lockScreenState = MessageTray.State.HIDDEN; this._lockScreenGroup.hide(); }, onCompleteScope: this, }); } else { this._lockScreenState = MessageTray.State.HIDDEN; this._lockScreenGroup.hide(); } global.stage.show_cursor(); if (Main.sessionMode.currentMode == 'lock-screen') Main.sessionMode.popMode('lock-screen'); }, _ensureUnlockDialog: function(onPrimary, allowCancel) { if (!this._dialog) { let constructor = Main.sessionMode.unlockDialog; if (!constructor) { // This session mode has no locking capabilities this.unlock(); return; } this._dialog = new constructor(this._lockDialogGroup); let time = global.get_current_time(); this._dialog.connect('loaded', Lang.bind(this, function() { if (!this._dialog.open(time, onPrimary)) { log('Could not open login dialog: failed to acquire grab'); this.unlock(); } })); this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed)); this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded)); } this._dialog.allowCancel = allowCancel; }, _onUnlockFailed: function() { this._resetLockScreen(true, false); }, _onUnlockSucceded: function() { this._tweenUnlocked(); }, _resetLockScreen: function(animateLockScreen, animateLockDialog) { if (this._lockScreenState == MessageTray.State.SHOWING || this._lockScreenState == MessageTray.State.SHOWN) return; this._ensureLockScreen(); this._lockDialogGroup.scale_x = 1; this._lockDialogGroup.scale_y = 1; this._lockScreenGroup.show(); this._lockScreenState = MessageTray.State.SHOWING; if (animateLockScreen) { this._lockScreenGroup.y = -global.screen_height; Tweener.removeTweens(this._lockScreenGroup); Tweener.addTween(this._lockScreenGroup, { y: 0, time: SHORT_FADE_TIME, transition: 'easeOutQuad', onComplete: function() { this._lockScreenShown(); }, onCompleteScope: this }); } else { this._lockScreenGroup.fixed_position_set = false; this._lockScreenShown(); } if (animateLockDialog) { this._lockDialogGroup.opacity = 0; Tweener.removeTweens(this._lockDialogGroup); Tweener.addTween(this._lockDialogGroup, { opacity: 255, time: SHORT_FADE_TIME, transition: 'easeOutQuad' }); } else { this._lockDialogGroup.opacity = 255; } this._lockScreenGroup.grab_key_focus(); if (Main.sessionMode.currentMode != 'lock-screen') Main.sessionMode.pushMode('lock-screen'); }, _lockScreenShown: function() { if (this._dialog && !this._isGreeter) { this._dialog.destroy(); this._dialog = null; } if (this._arrowAnimationId) Mainloop.source_remove(this._arrowAnimationId); this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows)); this._animateArrows(); let motionId = global.stage.connect('captured-event', function(stage, event) { if (event.type() == Clutter.EventType.MOTION) { global.stage.show_cursor(); global.stage.disconnect(motionId); } return false; }); global.stage.hide_cursor(); this._lockScreenState = MessageTray.State.SHOWN; this._lockScreenGroup.fixed_position_set = false; this._lockScreenScrollCounter = 0; this.emit('lock-screen-shown'); }, // Some of the actors in the lock screen are heavy in // resources, so we only create them when needed _ensureLockScreen: function() { if (this._hasLockScreen) return; this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER, x_expand: true, y_expand: true, vertical: true, style_class: 'screen-shield-contents-box' }); this._clock = new Clock(); this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true, y_fill: true }); this._lockScreenContents.add_actor(this._lockScreenContentsBox); if (this._settings.get_boolean('show-notifications')) { this._notificationsBox = new NotificationsBox(); this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true, y_fill: true, expand: true }); } this._hasLockScreen = true; }, _clearLockScreen: function() { this._clock.destroy(); this._clock = null; if (this._notificationsBox) { this._notificationsBox.destroy(); this._notificationsBox = null; } this._lockScreenContentsBox.destroy(); if (this._arrowAnimationId) { Mainloop.source_remove(this._arrowAnimationId); this._arrowAnimationId = 0; } this._hasLockScreen = false; }, get locked() { return this._isActive; }, get activationTime() { return this._activationTime; }, _tweenUnlocked: function() { this._inUnlockAnimation = true; this.unlock(); Tweener.addTween(this._lockDialogGroup, { scale_x: 0, scale_y: 0, time: Overview.ANIMATION_TIME, transition: 'easeOutQuad', onComplete: function() { if (this._dialog) { this._dialog.destroy(); this._dialog = null; } this.actor.hide(); this._inUnlockAnimation = false; }, onCompleteScope: this }); }, unlock: function() { if (this._hasLockScreen) this._clearLockScreen(); if (this._dialog && !this._isGreeter) { this._dialog.destroy(); this._dialog = null; } this._lightbox.hide(); if (this._isModal) { Main.popModal(this.actor); this._isModal = false; } if (!this._inUnlockAnimation) this.actor.hide(); if (Main.sessionMode.currentMode == 'lock-screen') Main.sessionMode.popMode('lock-screen'); if (Main.sessionMode.currentMode == 'unlock-dialog') Main.sessionMode.popMode('unlock-dialog'); this._activationTime = 0; this._isActive = false; this._isLocked = false; this.emit('lock-status-changed'); }, lock: function(animate) { if (!this._isModal) { Main.pushModal(this.actor, { keybindingMode: Main.KeybindingMode.LOCK_SCREEN }); this._isModal = true; } if (this._activationTime == 0) this._activationTime = GLib.get_monotonic_time(); this.actor.show(); if (Main.sessionMode.currentMode != 'unlock-dialog' && Main.sessionMode.currentMode != 'lock-screen') { this._isGreeter = Main.sessionMode.isGreeter; if (!this._isGreeter) Main.sessionMode.pushMode('unlock-dialog'); } this._resetLockScreen(animate, animate); this._isActive = true; this._isLocked = true; this.emit('lock-status-changed'); }, }); Signals.addSignalMethods(ScreenShield.prototype); /* Fallback code to handle session locking using gnome-screensaver, in case the required GDM dependency is not there */ const ScreenShieldFallback = new Lang.Class({ Name: 'ScreenShieldFallback', _init: function() { Util.spawn(['gnome-screensaver']); this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session, g_name: 'org.gnome.ScreenSaver', g_object_path: '/org/gnome/ScreenSaver', g_interface_name: 'org.gnome.ScreenSaver', g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES), }); this._proxy.init(null); this._proxy.connect('g-signal', Lang.bind(this, this._onSignal)); this._proxy.connect('notify::g-name-owner', Lang.bind(this, this._onNameOwnerChanged)); }, _onNameOwnerChanged: function(object, pspec) { if (this._proxy.g_name_owner) [this._locked] = this._proxy.call_sync('GetActive', null, Gio.DBusCallFlags.NONE, -1, null).deep_unpack(); else this._locked = false; this.emit('lock-status-changed', this._locked); }, _onSignal: function(proxy, senderName, signalName, params) { if (signalName == 'ActiveChanged') { [this._locked] = params.deep_unpack(); this.emit('lock-status-changed', this._locked); } }, get locked() { return this._locked; }, lock: function() { this._proxy.call('Lock', null, Gio.DBusCallFlags.NONE, -1, null, Lang.bind(this, function(proxy, result) { proxy.call_finish(result); this.emit('lock-screen-shown'); })); }, unlock: function() { this._proxy.call('SetActive', GLib.Variant.new('(b)', false), Gio.DBusCallFlags.NONE, -1, null, null); }, }); Signals.addSignalMethods(ScreenShieldFallback.prototype);