~ubuntu-branches/ubuntu/utopic/gnome-shell/utopic

« back to all changes in this revision

Viewing changes to .pc/git_relock_screen_after_crash.patch/js/ui/screenShield.js

  • Committer: Package Import Robot
  • Author(s): Tim Lunn
  • Date: 2013-05-27 08:07:15 UTC
  • Revision ID: package-import@ubuntu.com-20130527080715-de6i2e5u8182cpet
Tags: 3.6.3.1-0ubuntu9
* debian/patches/git_relock_screen_after_crash.patch:
  - backport upstream patch to relock session after crash (LP: #1064582)
 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
2
 
 
3
const Cairo = imports.cairo;
 
4
const Clutter = imports.gi.Clutter;
 
5
const Gio = imports.gi.Gio;
 
6
const GLib = imports.gi.GLib;
 
7
const GnomeDesktop = imports.gi.GnomeDesktop;
 
8
const Lang = imports.lang;
 
9
const Mainloop = imports.mainloop;
 
10
const Meta = imports.gi.Meta;
 
11
const Signals = imports.signals;
 
12
const Shell = imports.gi.Shell;
 
13
const St = imports.gi.St;
 
14
const TweenerEquations = imports.tweener.equations;
 
15
 
 
16
const GnomeSession = imports.misc.gnomeSession;
 
17
const Layout = imports.ui.layout;
 
18
const LoginManager = imports.misc.loginManager;
 
19
const Lightbox = imports.ui.lightbox;
 
20
const Main = imports.ui.main;
 
21
const Overview = imports.ui.overview;
 
22
const MessageTray = imports.ui.messageTray;
 
23
const ShellDBus = imports.ui.shellDBus;
 
24
const Tweener = imports.ui.tweener;
 
25
const Util = imports.misc.util;
 
26
 
 
27
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
 
28
const LOCK_ENABLED_KEY = 'lock-enabled';
 
29
const LOCK_DELAY_KEY = 'lock-delay';
 
30
 
 
31
const CURTAIN_SLIDE_TIME = 0.3;
 
32
// fraction of screen height the arrow must reach before completing
 
33
// the slide up automatically
 
34
const ARROW_DRAG_THRESHOLD = 0.1;
 
35
 
 
36
// Parameters for the arrow animation
 
37
const N_ARROWS = 3;
 
38
const ARROW_ANIMATION_TIME = 0.6;
 
39
const ARROW_ANIMATION_PEAK_OPACITY = 0.4;
 
40
 
 
41
// The distance in px that the lock screen will move to when pressing
 
42
// a key that has no effect in the lock screen (bumping it)
 
43
const BUMP_SIZE = 25;
 
44
const BUMP_TIME = 0.3;
 
45
 
 
46
const SUMMARY_ICON_SIZE = 48;
 
47
 
 
48
// Lightbox fading times
 
49
// STANDARD_FADE_TIME is used when the session goes idle, while
 
50
// SHORT_FADE_TIME is used when requesting lock explicitly from the user menu
 
51
const STANDARD_FADE_TIME = 10;
 
52
const SHORT_FADE_TIME = 0.3;
 
53
 
 
54
const Clock = new Lang.Class({
 
55
    Name: 'ScreenShieldClock',
 
56
 
 
57
    CLOCK_FORMAT_KEY: 'clock-format',
 
58
    CLOCK_SHOW_SECONDS_KEY: 'clock-show-seconds',
 
59
 
 
60
    _init: function() {
 
61
        this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock',
 
62
                                        vertical: true });
 
63
 
 
64
        this._time = new St.Label({ style_class: 'screen-shield-clock-time' });
 
65
        this._date = new St.Label({ style_class: 'screen-shield-clock-date' });
 
66
 
 
67
        this.actor.add(this._time, { x_align: St.Align.MIDDLE });
 
68
        this.actor.add(this._date, { x_align: St.Align.MIDDLE });
 
69
 
 
70
        this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
 
71
        this._wallClock.connect('notify::clock', Lang.bind(this, this._updateClock));
 
72
 
 
73
        this._updateClock();
 
74
    },
 
75
 
 
76
    _updateClock: function() {
 
77
        this._time.text = this._wallClock.clock;
 
78
 
 
79
        let date = new Date();
 
80
        /* Translators: This is a time format for a date in
 
81
           long format */
 
82
        this._date.text = date.toLocaleFormat(_("%A, %B %d"));
 
83
    },
 
84
 
 
85
    destroy: function() {
 
86
        this.actor.destroy();
 
87
        this._wallClock.run_dispose();
 
88
    }
 
89
});
 
90
 
 
91
const NotificationsBox = new Lang.Class({
 
92
    Name: 'NotificationsBox',
 
93
 
 
94
    _init: function() {
 
95
        this.actor = new St.BoxLayout({ vertical: true,
 
96
                                        name: 'screenShieldNotifications',
 
97
                                        style_class: 'screen-shield-notifications-box' });
 
98
 
 
99
        this._residentNotificationBox = new St.BoxLayout({ vertical: true,
 
100
                                                           style_class: 'screen-shield-notifications-box' });
 
101
        let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
 
102
        this._persistentNotificationBox = new St.BoxLayout({ vertical: true,
 
103
                                                             style_class: 'screen-shield-notifications-box' });
 
104
        scrollView.add_actor(this._persistentNotificationBox);
 
105
 
 
106
        this.actor.add(this._residentNotificationBox, { x_fill: true });
 
107
        this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
 
108
 
 
109
        this._items = [];
 
110
        Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) {
 
111
            this._summaryItemAdded(Main.messageTray, item, true);
 
112
        }));
 
113
        this._updateVisibility();
 
114
 
 
115
        this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded));
 
116
    },
 
117
 
 
118
    destroy: function() {
 
119
        if (this._summaryAddedId) {
 
120
            Main.messageTray.disconnect(this._summaryAddedId);
 
121
            this._summaryAddedId = 0;
 
122
        }
 
123
 
 
124
        for (let i = 0; i < this._items.length; i++)
 
125
            this._removeItem(this._items[i]);
 
126
        this._items = [];
 
127
 
 
128
        this.actor.destroy();
 
129
    },
 
130
 
 
131
    _updateVisibility: function() {
 
132
        this._residentNotificationBox.visible = this._residentNotificationBox.get_n_children() > 0;
 
133
        this._persistentNotificationBox.visible = this._persistentNotificationBox.get_children().some(function(a) {
 
134
            return a.visible;
 
135
        });
 
136
 
 
137
        this.actor.visible = this._residentNotificationBox.visible || this._persistentNotificationBox.visible;
 
138
    },
 
139
 
 
140
    _sourceIsResident: function(source) {
 
141
        return source.hasResidentNotification() && !source.isChat;
 
142
    },
 
143
 
 
144
    _makeNotificationCountText: function(count, isChat) {
 
145
        if (isChat)
 
146
            return ngettext("%d new message", "%d new messages", count).format(count);
 
147
        else
 
148
            return ngettext("%d new notification", "%d new notifications", count).format(count);
 
149
    },
 
150
 
 
151
    _makeNotificationSource: function(source) {
 
152
        let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
 
153
 
 
154
        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
 
155
        box.add(sourceActor.actor, { y_fill: true });
 
156
 
 
157
        let textBox = new St.BoxLayout({ vertical: true });
 
158
        box.add(textBox, { y_fill: false, y_align: St.Align.START });
 
159
 
 
160
        let label = new St.Label({ text: source.title,
 
161
                                   style_class: 'screen-shield-notification-label' });
 
162
        textBox.add(label);
 
163
 
 
164
        let count = source.unseenCount;
 
165
        let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
 
166
                                        style_class: 'screen-shield-notification-count-text' });
 
167
        textBox.add(countLabel);
 
168
 
 
169
        box.visible = count != 0;
 
170
        return [box, countLabel];
 
171
    },
 
172
 
 
173
    _summaryItemAdded: function(tray, item, dontUpdateVisibility) {
 
174
        // Ignore transient sources, or sources explicitly marked not to show
 
175
        // in the lock screen
 
176
        if (item.source.isTransient || !item.source.showInLockScreen)
 
177
            return;
 
178
 
 
179
        let obj = {
 
180
            item: item,
 
181
            source: item.source,
 
182
            resident: this._sourceIsResident(item.source),
 
183
            contentUpdatedId: 0,
 
184
            sourceDestroyId: 0,
 
185
            sourceBox: null,
 
186
            countLabel: null,
 
187
        };
 
188
 
 
189
        if (obj.resident) {
 
190
            this._residentNotificationBox.add(item.notificationStackWidget);
 
191
            item.closeButton.hide();
 
192
            item.prepareNotificationStackForShowing();
 
193
        } else {
 
194
            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source);
 
195
            this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
 
196
        }
 
197
 
 
198
        obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated));
 
199
        obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged));
 
200
        obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged));
 
201
        obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
 
202
        this._items.push(obj);
 
203
 
 
204
        if (!dontUpdateVisibility)
 
205
            this._updateVisibility();
 
206
    },
 
207
 
 
208
    _findSource: function(source) {
 
209
        for (let i = 0; i < this._items.length; i++) {
 
210
            if (this._items[i].source == source)
 
211
                return i;
 
212
        }
 
213
 
 
214
        return -1;
 
215
    },
 
216
 
 
217
    _onItemContentUpdated: function(item) {
 
218
        let obj = this._items[this._findSource(item.source)];
 
219
        this._updateItem(obj);
 
220
    },
 
221
 
 
222
    _onSourceChanged: function(source) {
 
223
        let obj = this._items[this._findSource(source)];
 
224
        this._updateItem(obj);
 
225
    },
 
226
 
 
227
    _updateItem: function(obj) {
 
228
        let itemShouldBeResident = this._sourceIsResident(obj.source);
 
229
 
 
230
        if (itemShouldBeResident && obj.resident) {
 
231
            // Nothing to do here, the actor is already updated
 
232
            return;
 
233
        }
 
234
 
 
235
        if (obj.resident && !itemShouldBeResident) {
 
236
            // make into a regular item
 
237
            obj.item.doneShowingNotificationStack();
 
238
            this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
 
239
 
 
240
            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source);
 
241
            this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
 
242
        } else if (itemShouldBeResident && !obj.resident) {
 
243
            // make into a resident item
 
244
            obj.sourceBox.destroy();
 
245
            obj.sourceBox = obj.countLabel = null;
 
246
            obj.resident = true;
 
247
 
 
248
            this._residentNotificationBox.add(obj.item.notificationStackWidget);
 
249
            obj.item.closeButton.hide();
 
250
            obj.item.prepareNotificationStackForShowing();
 
251
        } else {
 
252
            // just update the counter
 
253
            let count = obj.source.unseenCount;
 
254
            obj.countLabel.text = this._makeNotificationCountText(count, obj.source.isChat);
 
255
            obj.sourceBox.visible = count != 0;
 
256
        }
 
257
 
 
258
        this._updateVisibility();
 
259
    },
 
260
 
 
261
    _onSourceDestroy: function(source) {
 
262
        let idx = this._findSource(source);
 
263
 
 
264
        this._removeItem(this._items[idx]);
 
265
        this._items.splice(idx, 1);
 
266
 
 
267
        this._updateVisibility();
 
268
    },
 
269
 
 
270
    _removeItem: function(obj) {
 
271
        if (obj.resident) {
 
272
            obj.item.doneShowingNotificationStack();
 
273
            this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
 
274
        } else {
 
275
            obj.sourceBox.destroy();
 
276
        }
 
277
 
 
278
        obj.item.disconnect(obj.contentUpdatedId);
 
279
        obj.source.disconnect(obj.sourceDestroyId);
 
280
        obj.source.disconnect(obj.sourceCountChangedId);
 
281
    },
 
282
});
 
283
 
 
284
const Arrow = new Lang.Class({
 
285
    Name: 'Arrow',
 
286
    Extends: St.Bin,
 
287
 
 
288
    _init: function(params) {
 
289
        this.parent(params);
 
290
        this.x_fill = this.y_fill = true;
 
291
        this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
 
292
 
 
293
        this._drawingArea = new St.DrawingArea();
 
294
        this._drawingArea.connect('repaint', Lang.bind(this, this._drawArrow));
 
295
        this.child = this._drawingArea;
 
296
 
 
297
        this._shadowHelper = null;
 
298
        this._shadowWidth = this._shadowHeight = 0;
 
299
    },
 
300
 
 
301
    _drawArrow: function(arrow) {
 
302
        let cr = arrow.get_context();
 
303
        let [w, h] = arrow.get_surface_size();
 
304
        let node = this.get_theme_node();
 
305
        let thickness = node.get_length('-arrow-thickness');
 
306
 
 
307
        Clutter.cairo_set_source_color(cr, node.get_foreground_color());
 
308
 
 
309
        cr.setLineCap(Cairo.LineCap.ROUND);
 
310
        cr.setLineWidth(thickness);
 
311
 
 
312
        cr.moveTo(thickness / 2, h - thickness / 2);
 
313
        cr.lineTo(w/2, thickness);
 
314
        cr.lineTo(w - thickness / 2, h - thickness / 2);
 
315
        cr.stroke();
 
316
    },
 
317
 
 
318
    vfunc_style_changed: function() {
 
319
        let node = this.get_theme_node();
 
320
        this._shadow = node.get_shadow('-arrow-shadow');
 
321
        if (this._shadow)
 
322
            this._shadowHelper = St.ShadowHelper.new(this._shadow);
 
323
        else
 
324
            this._shadowHelper = null;
 
325
    },
 
326
 
 
327
    vfunc_paint: function() {
 
328
        if (this._shadowHelper) {
 
329
            this._shadowHelper.update(this._drawingArea);
 
330
 
 
331
            let allocation = this._drawingArea.get_allocation_box();
 
332
            let paintOpacity = this._drawingArea.get_paint_opacity();
 
333
            this._shadowHelper.paint(allocation, paintOpacity);
 
334
        }
 
335
 
 
336
        this._drawingArea.paint();
 
337
    }
 
338
});
 
339
 
 
340
/**
 
341
 * To test screen shield, make sure to kill gnome-screensaver.
 
342
 *
 
343
 * If you are setting org.gnome.desktop.session.idle-delay directly in dconf,
 
344
 * rather than through System Settings, you also need to set
 
345
 * org.gnome.settings-daemon.plugins.power.sleep-display-ac and
 
346
 * org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value.
 
347
 * This will ensure that the screen blanks at the right time when it fades out.
 
348
 * https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependance.
 
349
 */
 
350
const ScreenShield = new Lang.Class({
 
351
    Name: 'ScreenShield',
 
352
 
 
353
    _init: function() {
 
354
        this.actor = Main.layoutManager.screenShieldGroup;
 
355
 
 
356
        this._lockScreenState = MessageTray.State.HIDDEN;
 
357
        this._lockScreenGroup = new St.Widget({ x_expand: true,
 
358
                                                y_expand: true,
 
359
                                                reactive: true,
 
360
                                                can_focus: true,
 
361
                                                name: 'lockScreenGroup',
 
362
                                              });
 
363
        this._lockScreenGroup.connect('key-release-event',
 
364
                                      Lang.bind(this, this._onLockScreenKeyRelease));
 
365
        this._lockScreenGroup.connect('scroll-event',
 
366
                                      Lang.bind(this, this._onLockScreenScroll));
 
367
 
 
368
        this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(),
 
369
                                                   name: 'lockScreenContents' });
 
370
        this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
 
371
 
 
372
        this._background = new St.Bin({ style_class: 'screen-shield-background',
 
373
                                        child: Meta.BackgroundActor.new_for_screen(global.screen) });
 
374
        this._lockScreenGroup.add_actor(this._background);
 
375
        this._lockScreenGroup.add_actor(this._lockScreenContents);
 
376
 
 
377
        this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
 
378
                                                  vertical: true,
 
379
                                                  x_align: Clutter.ActorAlign.CENTER,
 
380
                                                  y_align: Clutter.ActorAlign.END,
 
381
                                                  // HACK: without these, ClutterBinLayout
 
382
                                                  // ignores alignment properties on the actor
 
383
                                                  x_expand: true,
 
384
                                                  y_expand: true });
 
385
 
 
386
        for (let i = 0; i < N_ARROWS; i++) {
 
387
            let arrow = new Arrow({ opacity: 0 });
 
388
            this._arrowContainer.add_actor(arrow);
 
389
        }
 
390
        this._lockScreenContents.add_actor(this._arrowContainer);
 
391
 
 
392
        let dragArea = new Clutter.Rect({ origin: new Clutter.Point({ x: 0, y: -global.screen_height, }),
 
393
                                          size: new Clutter.Size({ width: global.screen_width,
 
394
                                                                   height: global.screen_height }) });
 
395
        let action = new Clutter.DragAction({ drag_axis: Clutter.DragAxis.Y_AXIS,
 
396
                                              drag_area: dragArea });
 
397
        action.connect('drag-begin', Lang.bind(this, this._onDragBegin));
 
398
        action.connect('drag-end', Lang.bind(this, this._onDragEnd));
 
399
        this._lockScreenGroup.add_action(action);
 
400
 
 
401
        this._lockDialogGroup = new St.Widget({ x_expand: true,
 
402
                                                y_expand: true,
 
403
                                                pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
 
404
                                                name: 'lockDialogGroup' });
 
405
 
 
406
        this.actor.add_actor(this._lockDialogGroup);
 
407
        this.actor.add_actor(this._lockScreenGroup);
 
408
 
 
409
        this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) {
 
410
            if (error) {
 
411
                logError(error, 'Error while reading gnome-session presence');
 
412
                return;
 
413
            }
 
414
 
 
415
            this._onStatusChanged(proxy.status);
 
416
        }));
 
417
        this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
 
418
            this._onStatusChanged(status);
 
419
        }));
 
420
 
 
421
        this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);
 
422
 
 
423
        this._loginManager = LoginManager.getLoginManager();
 
424
        this._loginSession = this._loginManager.getCurrentSessionProxy();
 
425
        this._loginSession.connectSignal('Lock', Lang.bind(this, function() { this.lock(false); }));
 
426
        this._loginSession.connectSignal('Unlock', Lang.bind(this, function() { this.unlock(); }));
 
427
 
 
428
        this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
 
429
 
 
430
        this._isModal = false;
 
431
        this._hasLockScreen = false;
 
432
        this._isGreeter = false;
 
433
        this._isActive = false;
 
434
        this._inUnlockAnimation = false;
 
435
        this._activationTime = 0;
 
436
 
 
437
        this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
 
438
                                               { inhibitEvents: true,
 
439
                                                 fadeInTime: STANDARD_FADE_TIME,
 
440
                                                 fadeFactor: 1 });
 
441
 
 
442
        this.idleMonitor = Shell.IdleMonitor.get();
 
443
    },
 
444
 
 
445
    _becomeModal: function() {
 
446
        if (this._isModal)
 
447
            return true;
 
448
 
 
449
        this._isModal = Main.pushModal(this.actor);
 
450
        if (this._isModal)
 
451
            return true;
 
452
 
 
453
        // We failed to get a pointer grab, it means that
 
454
        // something else has it. Try with a keyboard grab only
 
455
        this._isModal = Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED });
 
456
        return this._isModal;
 
457
    },
 
458
 
 
459
    _onLockScreenKeyRelease: function(actor, event) {
 
460
        let symbol = event.get_key_symbol();
 
461
 
 
462
        // Do nothing if the lock screen is not fully shown.
 
463
        // This avoids reusing the previous (and stale) unlock
 
464
        // dialog if esc is pressed while the curtain is going
 
465
        // down after cancel.
 
466
        // Similarly, don't bump if the lock screen is not showing or is
 
467
        // animating, as the bump overrides the animation and would
 
468
        // remove any onComplete handler.
 
469
 
 
470
        if (this._lockScreenState != MessageTray.State.SHOWN)
 
471
            return false;
 
472
 
 
473
        if (symbol == Clutter.KEY_Escape ||
 
474
            symbol == Clutter.KEY_Return ||
 
475
            symbol == Clutter.KEY_KP_Enter) {
 
476
            this._ensureUnlockDialog(true, true);
 
477
            this._hideLockScreen(true);
 
478
            return true;
 
479
        }
 
480
 
 
481
        this._bumpLockScreen();
 
482
        return true;
 
483
    },
 
484
 
 
485
    _onLockScreenScroll: function(actor, event) {
 
486
        if (this._lockScreenState != MessageTray.State.SHOWN)
 
487
            return false;
 
488
 
 
489
        let delta = 0;
 
490
        if (event.get_scroll_direction() == Clutter.ScrollDirection.UP)
 
491
            delta = 5;
 
492
        else if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
 
493
            delta = Math.max(0, event.get_scroll_delta()[0]);
 
494
 
 
495
        this._lockScreenScrollCounter += delta;
 
496
 
 
497
        // 7 standard scrolls to lift up
 
498
        if (this._lockScreenScrollCounter > 35) {
 
499
            this._ensureUnlockDialog(false, true);
 
500
            this._hideLockScreen(true);
 
501
        }
 
502
 
 
503
        return true;
 
504
    },
 
505
 
 
506
    _animateArrows: function() {
 
507
        let arrows = this._arrowContainer.get_children();
 
508
        let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1);
 
509
        let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY;
 
510
        for (let i = 0; i < arrows.length; i++) {
 
511
            arrows.opacity = 0;
 
512
            Tweener.addTween(arrows[i],
 
513
                             { opacity: 0,
 
514
                               delay: unitaryDelay * (N_ARROWS - (i + 1)),
 
515
                               time: ARROW_ANIMATION_TIME,
 
516
                               transition: function(t, b, c, d) {
 
517
                                 if (t < d/2)
 
518
                                     return TweenerEquations.easeOutQuad(t, 0, maxOpacity, d/2);
 
519
                                 else
 
520
                                     return TweenerEquations.easeInQuad(t - d/2, maxOpacity, -maxOpacity, d/2);
 
521
                               }
 
522
                             });
 
523
        }
 
524
 
 
525
        return true;
 
526
    },
 
527
 
 
528
    _onDragBegin: function() {
 
529
        Tweener.removeTweens(this._lockScreenGroup);
 
530
        this._lockScreenState = MessageTray.State.HIDING;
 
531
        this._ensureUnlockDialog(false, false);
 
532
    },
 
533
 
 
534
    _onDragEnd: function(action, actor, eventX, eventY, modifiers) {
 
535
        if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
 
536
            // Complete motion automatically
 
537
            this._hideLockScreen(true);
 
538
            this._ensureUnlockDialog(false, true);
 
539
        } else {
 
540
            // restore the lock screen to its original place
 
541
            // try to use the same speed as the normal animation
 
542
            let h = global.stage.height;
 
543
            let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h;
 
544
            Tweener.removeTweens(this._lockScreenGroup);
 
545
            Tweener.addTween(this._lockScreenGroup,
 
546
                             { y: 0,
 
547
                               time: time,
 
548
                               transition: 'easeInQuad',
 
549
                               onComplete: function() {
 
550
                                   this._lockScreenGroup.fixed_position_set = false;
 
551
                                   this._lockScreenState = MessageTray.State.SHOWN;
 
552
                               },
 
553
                               onCompleteScope: this,
 
554
                             });
 
555
 
 
556
            // If we have a unlock dialog, cancel it
 
557
            if (this._dialog) {
 
558
                this._dialog.cancel();
 
559
                if (!this._isGreeter) {
 
560
                    this._dialog = null;
 
561
                }
 
562
            }
 
563
        }
 
564
    },
 
565
 
 
566
    _onStatusChanged: function(status) {
 
567
        if (status != GnomeSession.PresenceStatus.IDLE)
 
568
            return;
 
569
 
 
570
        if (this._dialog) {
 
571
            this._dialog.cancel();
 
572
            if (!this._isGreeter) {
 
573
                this._dialog = null;
 
574
            }
 
575
        }
 
576
 
 
577
        if (!this._becomeModal()) {
 
578
            // We could not become modal, so we can't activate the
 
579
            // screenshield. The user is probably very upset at this
 
580
            // point, but any application using global grabs is broken
 
581
            // Just tell him to stop using this app
 
582
            //
 
583
            // XXX: another option is to kick the user into the gdm login
 
584
            // screen, where we're not affected by grabs
 
585
            Main.notifyError("Unable to lock",
 
586
                             "Lock was blocked by an application");
 
587
            return;
 
588
        }
 
589
 
 
590
        if (!this._isActive) {
 
591
            this._lightbox.show();
 
592
 
 
593
            if (this._activationTime == 0)
 
594
                this._activationTime = GLib.get_monotonic_time();
 
595
 
 
596
            // What we want is a negative transition to 0, so we install a watch for
 
597
            // 1 second and trigger it if the idle_time becomes lower than that
 
598
            // The correct fix will come in GNOME 3.8 with GnomeDesktop.IdleMonitor
 
599
            this._becameActiveId = this.idleMonitor.add_watch(1000, Lang.bind(this, function() {
 
600
                if (this.idleMonitor.get_idletime() >= 1000)
 
601
                    return;
 
602
 
 
603
                this.idleMonitor.remove_watch(this._becameActiveId);
 
604
 
 
605
                let lightboxWasShown = this._lightbox.shown;
 
606
                this._lightbox.hide();
 
607
 
 
608
                // GLib.get_monotonic_time() returns microseconds, convert to seconds
 
609
                let elapsedTime = (GLib.get_monotonic_time() - this._activationTime) / 1000000;
 
610
                let shouldLock = lightboxWasShown &&
 
611
                    this._settings.get_boolean(LOCK_ENABLED_KEY) &&
 
612
                    (elapsedTime >= this._settings.get_uint(LOCK_DELAY_KEY));
 
613
                if (shouldLock || this._isLocked) {
 
614
                    this.lock(false);
 
615
                } else if (this._isActive) {
 
616
                    this.unlock();
 
617
                }
 
618
            }));
 
619
 
 
620
            this._isActive = true;
 
621
            this.emit('lock-status-changed');
 
622
        }
 
623
    },
 
624
 
 
625
    showDialog: function() {
 
626
        // Ensure that the stage window is mapped, before taking a grab
 
627
        // otherwise X errors out
 
628
        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
 
629
            if (!this._becomeModal()) {
 
630
                // In the login screen, this is a hard error. Fail-whale
 
631
                log('Could not acquire modal grab for the login screen. Aborting login process.');
 
632
                Meta.quit(Meta.ExitCode.ERROR);
 
633
            }
 
634
 
 
635
            return false;
 
636
        }));
 
637
 
 
638
        this.actor.show();
 
639
        this._isGreeter = Main.sessionMode.isGreeter;
 
640
        this._ensureUnlockDialog(true, true);
 
641
        this._hideLockScreen(false);
 
642
    },
 
643
 
 
644
    _bumpLockScreen: function() {
 
645
        Tweener.removeTweens(this._lockScreenGroup);
 
646
        Tweener.addTween(this._lockScreenGroup,
 
647
                         { y: -BUMP_SIZE,
 
648
                           time: BUMP_TIME / 2,
 
649
                           transition: 'easeOutQuad',
 
650
                           onComplete: function() {
 
651
                               Tweener.addTween(this,
 
652
                                                { y: 0,
 
653
                                                  time: BUMP_TIME / 2,
 
654
                                                  transition: 'easeInQuad' });
 
655
                           }
 
656
                         });
 
657
    },
 
658
 
 
659
    _hideLockScreen: function(animate) {
 
660
        this._lockScreenState = MessageTray.State.HIDING;
 
661
 
 
662
        if (animate) {
 
663
            // Tween the lock screen out of screen
 
664
            // try to use the same speed regardless of original position
 
665
            let h = global.stage.height;
 
666
            let time = CURTAIN_SLIDE_TIME * (h + this._lockScreenGroup.y) / h;
 
667
            Tweener.removeTweens(this._lockScreenGroup);
 
668
            Tweener.addTween(this._lockScreenGroup,
 
669
                             { y: -h,
 
670
                               time: time,
 
671
                               transition: 'easeInQuad',
 
672
                               onComplete: function() {
 
673
                                   this._lockScreenState = MessageTray.State.HIDDEN;
 
674
                                   this._lockScreenGroup.hide();
 
675
                               },
 
676
                               onCompleteScope: this,
 
677
                             });
 
678
        } else {
 
679
            this._lockScreenState = MessageTray.State.HIDDEN;
 
680
            this._lockScreenGroup.hide();
 
681
        }
 
682
 
 
683
        if (Main.sessionMode.currentMode == 'lock-screen')
 
684
            Main.sessionMode.popMode('lock-screen');
 
685
    },
 
686
 
 
687
    _ensureUnlockDialog: function(onPrimary, allowCancel) {
 
688
        if (!this._dialog) {
 
689
            let constructor = Main.sessionMode.unlockDialog;
 
690
            if (!constructor) {
 
691
                // This session mode has no locking capabilities
 
692
                this.unlock();
 
693
                return;
 
694
            }
 
695
 
 
696
            this._dialog = new constructor(this._lockDialogGroup);
 
697
 
 
698
 
 
699
            let time = global.get_current_time();
 
700
            this._dialog.connect('loaded', Lang.bind(this, function() {
 
701
                if (!this._dialog.open(time, onPrimary)) {
 
702
                    log('Could not open login dialog: failed to acquire grab');
 
703
                    this.unlock();
 
704
                }
 
705
            }));
 
706
 
 
707
            this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
 
708
            this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
 
709
        }
 
710
 
 
711
        this._dialog.allowCancel = allowCancel;
 
712
    },
 
713
 
 
714
    _onUnlockFailed: function() {
 
715
        this._resetLockScreen(true, false);
 
716
    },
 
717
 
 
718
    _onUnlockSucceded: function() {
 
719
        this._tweenUnlocked();
 
720
    },
 
721
 
 
722
    _resetLockScreen: function(animateLockScreen, animateLockDialog) {
 
723
        this._ensureLockScreen();
 
724
        this._lockDialogGroup.scale_x = 1;
 
725
        this._lockDialogGroup.scale_y = 1;
 
726
 
 
727
        this._lockScreenGroup.show();
 
728
        this._lockScreenState = MessageTray.State.SHOWING;
 
729
 
 
730
        if (animateLockScreen) {
 
731
            this._lockScreenGroup.y = -global.screen_height;
 
732
            Tweener.removeTweens(this._lockScreenGroup);
 
733
            Tweener.addTween(this._lockScreenGroup,
 
734
                             { y: 0,
 
735
                               time: SHORT_FADE_TIME,
 
736
                               transition: 'easeOutQuad',
 
737
                               onComplete: function() {
 
738
                                   this._lockScreenShown();
 
739
                               },
 
740
                               onCompleteScope: this
 
741
                             });
 
742
        } else {
 
743
            this._lockScreenGroup.fixed_position_set = false;
 
744
            this._lockScreenShown();
 
745
        }
 
746
 
 
747
        if (animateLockDialog) {
 
748
            this._lockDialogGroup.opacity = 0;
 
749
            Tweener.removeTweens(this._lockDialogGroup);
 
750
            Tweener.addTween(this._lockDialogGroup,
 
751
                             { opacity: 255,
 
752
                               time: SHORT_FADE_TIME,
 
753
                               transition: 'easeOutQuad' });
 
754
        } else {
 
755
            this._lockDialogGroup.opacity = 255;
 
756
        }
 
757
 
 
758
        this._lockScreenGroup.grab_key_focus();
 
759
 
 
760
        if (Main.sessionMode.currentMode != 'lock-screen')
 
761
            Main.sessionMode.pushMode('lock-screen');
 
762
    },
 
763
 
 
764
    _lockScreenShown: function() {
 
765
        if (this._dialog && !this._isGreeter) {
 
766
            this._dialog.destroy();
 
767
            this._dialog = null;
 
768
        }
 
769
 
 
770
        if (this._arrowAnimationId)
 
771
            Mainloop.source_remove(this._arrowAnimationId);
 
772
        this._arrowAnimationId = Mainloop.timeout_add(6000, Lang.bind(this, this._animateArrows));
 
773
        this._animateArrows();
 
774
 
 
775
        this._lockScreenState = MessageTray.State.SHOWN;
 
776
        this._lockScreenGroup.fixed_position_set = false;
 
777
        this._lockScreenScrollCounter = 0;
 
778
 
 
779
        this.emit('lock-screen-shown');
 
780
    },
 
781
 
 
782
    // Some of the actors in the lock screen are heavy in
 
783
    // resources, so we only create them when needed
 
784
    _ensureLockScreen: function() {
 
785
        if (this._hasLockScreen)
 
786
            return;
 
787
 
 
788
        this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
 
789
                                                         y_align: Clutter.ActorAlign.CENTER,
 
790
                                                         x_expand: true,
 
791
                                                         y_expand: true,
 
792
                                                         vertical: true,
 
793
                                                         style_class: 'screen-shield-contents-box' });
 
794
        this._clock = new Clock();
 
795
        this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
 
796
                                                             y_fill: true });
 
797
 
 
798
        this._lockScreenContents.add_actor(this._lockScreenContentsBox);
 
799
 
 
800
        if (this._settings.get_boolean('show-notifications')) {
 
801
            this._notificationsBox = new NotificationsBox();
 
802
            this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
 
803
                                                                            y_fill: true,
 
804
                                                                            expand: true });
 
805
        }
 
806
 
 
807
        this._hasLockScreen = true;
 
808
    },
 
809
 
 
810
    _clearLockScreen: function() {
 
811
        this._clock.destroy();
 
812
        this._clock = null;
 
813
 
 
814
        if (this._notificationsBox) {
 
815
            this._notificationsBox.destroy();
 
816
            this._notificationsBox = null;
 
817
        }
 
818
 
 
819
        this._lockScreenContentsBox.destroy();
 
820
 
 
821
        if (this._arrowAnimationId) {
 
822
            Mainloop.source_remove(this._arrowAnimationId);
 
823
            this._arrowAnimationId = 0;
 
824
        }
 
825
 
 
826
        this._hasLockScreen = false;
 
827
    },
 
828
 
 
829
    get locked() {
 
830
        return this._isActive;
 
831
    },
 
832
 
 
833
    get activationTime() {
 
834
        return this._activationTime;
 
835
    },
 
836
 
 
837
    _tweenUnlocked: function() {
 
838
        this._inUnlockAnimation = true;
 
839
        this.unlock();
 
840
        Tweener.addTween(this._lockDialogGroup, {
 
841
            scale_x: 0,
 
842
            scale_y: 0,
 
843
            time: Overview.ANIMATION_TIME,
 
844
            transition: 'easeOutQuad',
 
845
            onComplete: function() {
 
846
                if (this._dialog) {
 
847
                    this._dialog.destroy();
 
848
                    this._dialog = null;
 
849
                }
 
850
                this.actor.hide();
 
851
                this._inUnlockAnimation = false;
 
852
            },
 
853
            onCompleteScope: this
 
854
        });
 
855
    },
 
856
 
 
857
    unlock: function() {
 
858
        if (this._hasLockScreen)
 
859
            this._clearLockScreen();
 
860
 
 
861
        if (this._dialog && !this._isGreeter) {
 
862
            this._dialog.destroy();
 
863
            this._dialog = null;
 
864
        }
 
865
 
 
866
        this._lightbox.hide();
 
867
 
 
868
        if (this._isModal) {
 
869
            Main.popModal(this.actor);
 
870
            this._isModal = false;
 
871
        }
 
872
 
 
873
        if (!this._inUnlockAnimation)
 
874
            this.actor.hide();
 
875
 
 
876
        if (Main.sessionMode.currentMode == 'lock-screen')
 
877
            Main.sessionMode.popMode('lock-screen');
 
878
        if (Main.sessionMode.currentMode == 'unlock-dialog')
 
879
            Main.sessionMode.popMode('unlock-dialog');
 
880
 
 
881
        this._activationTime = 0;
 
882
        this._isActive = false;
 
883
        this._isLocked = false;
 
884
        this.emit('lock-status-changed');
 
885
    },
 
886
 
 
887
    lock: function(animate) {
 
888
        // Warn the user if we can't become modal
 
889
        if (!this._becomeModal()) {
 
890
            Main.notifyError("Unable to lock",
 
891
                             "Lock was blocked by an application");
 
892
            return;
 
893
        }
 
894
 
 
895
        if (this._activationTime == 0)
 
896
            this._activationTime = GLib.get_monotonic_time();
 
897
 
 
898
        this.actor.show();
 
899
 
 
900
        if (Main.sessionMode.currentMode != 'unlock-dialog' &&
 
901
            Main.sessionMode.currentMode != 'lock-screen') {
 
902
            this._isGreeter = Main.sessionMode.isGreeter;
 
903
            if (!this._isGreeter)
 
904
                Main.sessionMode.pushMode('unlock-dialog');
 
905
        }
 
906
 
 
907
        this._resetLockScreen(animate, animate);
 
908
 
 
909
        this._isActive = true;
 
910
        this._isLocked = true;
 
911
        this.emit('lock-status-changed');
 
912
    },
 
913
});
 
914
Signals.addSignalMethods(ScreenShield.prototype);
 
915
 
 
916
/* Fallback code to handle session locking using gnome-screensaver,
 
917
   in case the required GDM dependency is not there
 
918
*/
 
919
const ScreenShieldFallback = new Lang.Class({
 
920
    Name: 'ScreenShieldFallback',
 
921
 
 
922
    _init: function() {
 
923
        Util.spawn(['gnome-screensaver']);
 
924
 
 
925
        this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
 
926
                                          g_name: 'org.gnome.ScreenSaver',
 
927
                                          g_object_path: '/org/gnome/ScreenSaver',
 
928
                                          g_interface_name: 'org.gnome.ScreenSaver',
 
929
                                          g_flags: (Gio.DBusProxyFlags.DO_NOT_AUTO_START |
 
930
                                                    Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES),
 
931
                                        });
 
932
        this._proxy.init(null);
 
933
 
 
934
        this._proxy.connect('g-signal', Lang.bind(this, this._onSignal));
 
935
        this._proxy.connect('notify::g-name-owner', Lang.bind(this, this._onNameOwnerChanged));
 
936
    },
 
937
 
 
938
    _onNameOwnerChanged: function(object, pspec) {
 
939
        if (this._proxy.g_name_owner)
 
940
            [this._locked] = this._proxy.call_sync('GetActive', null,
 
941
                                                   Gio.DBusCallFlags.NONE, -1, null).deep_unpack();
 
942
        else
 
943
            this._locked = false;
 
944
 
 
945
        this.emit('lock-status-changed', this._locked);
 
946
    },
 
947
 
 
948
    _onSignal: function(proxy, senderName, signalName, params) {
 
949
        if (signalName == 'ActiveChanged') {
 
950
            [this._locked] = params.deep_unpack();
 
951
            this.emit('lock-status-changed', this._locked);
 
952
        }
 
953
    },
 
954
 
 
955
    get locked() {
 
956
        return this._locked;
 
957
    },
 
958
 
 
959
    lock: function() {
 
960
        this._proxy.call('Lock', null, Gio.DBusCallFlags.NONE, -1, null,
 
961
                         Lang.bind(this, function(proxy, result) {
 
962
                             proxy.call_finish(result);
 
963
 
 
964
                             this.emit('lock-screen-shown');
 
965
                         }));
 
966
    },
 
967
 
 
968
    unlock: function() {
 
969
        this._proxy.call('SetActive', GLib.Variant.new('(b)', false),
 
970
                         Gio.DBusCallFlags.NONE, -1, null, null);
 
971
    },
 
972
});
 
973
Signals.addSignalMethods(ScreenShieldFallback.prototype);