~jbicha/ubuntu/oneiric/gnome-shell/oneiric-3.2.2.1

« back to all changes in this revision

Viewing changes to js/gdm/loginDialog.js

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2011-09-07 09:09:05 UTC
  • mfrom: (1.1.29 upstream)
  • Revision ID: package-import@ubuntu.com-20110907090905-kbo4fewcg12zt99u
Tags: 3.1.90.1-0ubuntu1
* New upstream release.
* debian/control: Bump build-depends on new mutter
* debian/patches/01_favorite_apps.patch: Updated
* debian/patches/03_remove-glx-dependency-on-armel.patch: Refreshed
* debian/patches/04_build-without-caribou.patch
  - Build without caribou since Ubuntu uses onboard and our System 
    Settings doesn't support choosing a different screen keyboard yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
 
2
 *
 
3
 * Copyright 2011 Red Hat, Inc
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation; either version 2, or (at your option)
 
8
 * any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this program; if not, write to the Free Software
 
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 
18
 * 02111-1307, USA.
 
19
 */
 
20
 
 
21
const AccountsService = imports.gi.AccountsService;
 
22
const Clutter = imports.gi.Clutter;
 
23
const CtrlAltTab = imports.ui.ctrlAltTab;
 
24
const Gio = imports.gi.Gio;
 
25
const GLib = imports.gi.GLib;
 
26
const Gtk = imports.gi.Gtk;
 
27
const Mainloop = imports.mainloop;
 
28
const Lang = imports.lang;
 
29
const Pango = imports.gi.Pango;
 
30
const Signals = imports.signals;
 
31
const Shell = imports.gi.Shell;
 
32
const St = imports.gi.St;
 
33
const GdmGreeter = imports.gi.GdmGreeter;
 
34
 
 
35
const Batch = imports.gdm.batch;
 
36
const Lightbox = imports.ui.lightbox;
 
37
const Main = imports.ui.main;
 
38
const ModalDialog = imports.ui.modalDialog;
 
39
const Tweener = imports.ui.tweener;
 
40
 
 
41
const _FADE_ANIMATION_TIME = 0.16;
 
42
const _RESIZE_ANIMATION_TIME = 0.25;
 
43
const _SCROLL_ANIMATION_TIME = 2.0;
 
44
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
 
45
 
 
46
let _loginDialog = null;
 
47
 
 
48
function _fadeInActor(actor) {
 
49
    let hold = new Batch.Hold();
 
50
 
 
51
    if (actor.opacity == 255 && actor.visible)
 
52
        return null;
 
53
 
 
54
    actor.show();
 
55
    let [minHeight, naturalHeight] = actor.get_preferred_height(-1);
 
56
 
 
57
    actor.opacity = 0;
 
58
    actor.set_height(0);
 
59
    Tweener.addTween(actor,
 
60
                     { opacity: 255,
 
61
                       height: naturalHeight,
 
62
                       time: _FADE_ANIMATION_TIME,
 
63
                       transition: 'easeOutQuad',
 
64
                       onComplete: function() {
 
65
                           actor.set_height(-1);
 
66
                           hold.release();
 
67
                       },
 
68
                       onCompleteScope: this
 
69
                     });
 
70
    return hold;
 
71
}
 
72
 
 
73
function _fadeOutActor(actor) {
 
74
    let hold = new Batch.Hold();
 
75
 
 
76
    if (!actor.visible) {
 
77
        actor.opacity = 0;
 
78
        return null;
 
79
    }
 
80
 
 
81
    if (actor.opacity == 0) {
 
82
        actor.hide();
 
83
        return null;
 
84
    }
 
85
 
 
86
    Tweener.addTween(actor,
 
87
                     { opacity: 0,
 
88
                       height: 0,
 
89
                       time: _FADE_ANIMATION_TIME,
 
90
                       transition: 'easeOutQuad',
 
91
                       onComplete: function() {
 
92
                           actor.hide();
 
93
                           actor.set_height(-1);
 
94
                           hold.release();
 
95
                       },
 
96
                       onCompleteScope: this
 
97
                     });
 
98
    return hold;
 
99
}
 
100
 
 
101
function _smoothlyResizeActor(actor, width, height) {
 
102
    let finalWidth;
 
103
    let finalHeight;
 
104
 
 
105
    if (width < 0)
 
106
        finalWidth = actor.width;
 
107
    else
 
108
        finalWidth = width;
 
109
 
 
110
    if (height < 0)
 
111
        finalHeight = actor.height;
 
112
    else
 
113
        finalHeight = height;
 
114
 
 
115
    actor.set_size(actor.width, actor.height);
 
116
 
 
117
    if (actor.width == finalWidth && actor.height == finalHeight)
 
118
        return null;
 
119
 
 
120
    let hold = new Batch.Hold();
 
121
 
 
122
    Tweener.addTween(actor,
 
123
                     { width: finalWidth,
 
124
                       height: finalHeight,
 
125
                       time: _RESIZE_ANIMATION_TIME,
 
126
                       transition: 'easeOutQuad',
 
127
                       onComplete: Lang.bind(this, function() {
 
128
                                       hold.release();
 
129
                                   })
 
130
                     });
 
131
    return hold;
 
132
}
 
133
 
 
134
function UserListItem(user, reason) {
 
135
    this._init(user, reason);
 
136
}
 
137
 
 
138
UserListItem.prototype = {
 
139
    _init: function(user) {
 
140
        this.user = user;
 
141
        this._userChangedId = this.user.connect('changed',
 
142
                                                 Lang.bind(this, this._onUserChanged));
 
143
 
 
144
        this._verticalBox = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-vertical-layout',
 
145
                                               vertical: true });
 
146
 
 
147
        this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
 
148
                                     can_focus: true,
 
149
                                     child: this._verticalBox,
 
150
                                     reactive: true,
 
151
                                     x_align: St.Align.START,
 
152
                                     x_fill: true });
 
153
        let layout = new St.BoxLayout({ vertical: false });
 
154
 
 
155
        this._verticalBox.add(layout,
 
156
                              { y_fill: true,
 
157
                                x_fill: true,
 
158
                                expand: true });
 
159
 
 
160
        this._focusBin = new St.Bin({ style_class: 'login-dialog-user-list-item-focus-bin' });
 
161
        this._verticalBox.add(this._focusBin,
 
162
                              { x_fill: false,
 
163
                                x_align: St.Align.MIDDLE,
 
164
                                y_fill: false,
 
165
                                expand: true });
 
166
 
 
167
        this._iconBin = new St.Bin();
 
168
        layout.add(this._iconBin);
 
169
        let textLayout = new St.BoxLayout({ style_class: 'login-dialog-user-list-item-text-box',
 
170
                                            vertical:    true });
 
171
        layout.add(textLayout,
 
172
                   { y_fill: false,
 
173
                     y_align: St.Align.MIDDLE,
 
174
                     expand: true });
 
175
 
 
176
        this._nameLabel = new St.Label({ text:        this.user.get_real_name(),
 
177
                                         style_class: 'login-dialog-user-list-item-name' });
 
178
        textLayout.add(this._nameLabel);
 
179
 
 
180
        this._updateIcon();
 
181
 
 
182
        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
 
183
    },
 
184
 
 
185
    _onUserChanged: function() {
 
186
        this._nameLabel.set_text(this.user.get_real_name());
 
187
        this._updateIcon();
 
188
    },
 
189
 
 
190
    _setIconFromFile: function(iconFile, styleClass) {
 
191
        if (styleClass)
 
192
            this._iconBin.set_style_class_name(styleClass);
 
193
        this._iconBin.set_style(null);
 
194
 
 
195
        this._iconBin.child = null;
 
196
        if (iconFile) {
 
197
            this._iconBin.show();
 
198
            // We use background-image instead of, say, St.TextureCache
 
199
            // so the theme writers can add a rounded frame around the image
 
200
            // and so theme writers can pick the icon size.
 
201
            this._iconBin.set_style('background-image: url("' + iconFile + '");');
 
202
        } else {
 
203
            this._iconBin.hide();
 
204
        }
 
205
    },
 
206
 
 
207
    _setIconFromName: function(iconName, styleClass) {
 
208
        if (styleClass)
 
209
            this._iconBin.set_style_class_name(styleClass);
 
210
        this._iconBin.set_style(null);
 
211
 
 
212
        if (iconName != null) {
 
213
            let icon = new St.Icon();
 
214
            icon.set_icon_name(iconName)
 
215
 
 
216
            this._iconBin.child = icon;
 
217
            this._iconBin.show();
 
218
        } else {
 
219
            this._iconBin.child = null;
 
220
            this._iconBin.hide();
 
221
        }
 
222
    },
 
223
 
 
224
    _updateIcon: function() {
 
225
        let iconFileName = this.user.get_icon_file();
 
226
        let gicon = null;
 
227
 
 
228
        if (GLib.file_test(iconFileName, GLib.FileTest.EXISTS))
 
229
            this._setIconFromFile(iconFileName, 'login-dialog-user-list-item-icon');
 
230
        else
 
231
            this._setIconFromName('avatar-default', 'login-dialog-user-list-item-icon');
 
232
    },
 
233
 
 
234
    _onClicked: function() {
 
235
        this.emit('activate');
 
236
    },
 
237
 
 
238
    fadeOutName: function() {
 
239
        return _fadeOutActor(this._nameLabel);
 
240
    },
 
241
 
 
242
    fadeInName: function() {
 
243
        return _fadeInActor(this._nameLabel);
 
244
    },
 
245
 
 
246
    showFocusAnimation: function(time) {
 
247
        let hold = new Batch.Hold();
 
248
 
 
249
        let box = this._verticalBox.get_allocation_box();
 
250
 
 
251
        Tweener.removeTweens(this._focusBin);
 
252
        this._focusBin.width = 0;
 
253
        Tweener.addTween(this._focusBin,
 
254
                         { width: box.x2 - box.x1,
 
255
                           time: time,
 
256
                           transition: 'linear',
 
257
                           onComplete: function() {
 
258
                               hold.release();
 
259
                           },
 
260
                           onCompleteScope: this
 
261
                         });
 
262
        return hold;
 
263
    }
 
264
 
 
265
};
 
266
Signals.addSignalMethods(UserListItem.prototype);
 
267
 
 
268
function UserList() {
 
269
    this._init.apply(this, arguments);
 
270
}
 
271
 
 
272
UserList.prototype = {
 
273
    _init: function() {
 
274
        this.actor = new St.ScrollView({ style_class: 'login-dialog-user-list-view'});
 
275
        this.actor.set_policy(Gtk.PolicyType.NEVER,
 
276
                              Gtk.PolicyType.AUTOMATIC);
 
277
 
 
278
        this._box = new St.BoxLayout({ vertical: true,
 
279
                                       style_class: 'login-dialog-user-list' });
 
280
 
 
281
        this.actor.add_actor(this._box,
 
282
                             { x_fill: true,
 
283
                               y_fill: true,
 
284
                               x_align: St.Align.START,
 
285
                               y_align: St.Align.MIDDLE });
 
286
        this._items = {};
 
287
    },
 
288
 
 
289
    _showItem: function(item) {
 
290
        let tasks = [function() {
 
291
                         return _fadeInActor(item.actor);
 
292
                     },
 
293
 
 
294
                     function() {
 
295
                         return item.fadeInName();
 
296
                     }];
 
297
 
 
298
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
299
        return batch.run();
 
300
    },
 
301
 
 
302
    _onItemActivated: function(activatedItem) {
 
303
        this.emit('activate', activatedItem);
 
304
    },
 
305
 
 
306
    giveUpWhitespace: function() {
 
307
        let container = this.actor.get_parent();
 
308
 
 
309
        container.child_set(this.actor, { expand: false });
 
310
    },
 
311
 
 
312
    takeOverWhitespace: function() {
 
313
        let container = this.actor.get_parent();
 
314
 
 
315
        container.child_set(this.actor, { expand: true });
 
316
    },
 
317
 
 
318
    pinInPlace: function() {
 
319
        this._box.set_size(this._box.width, this._box.height);
 
320
    },
 
321
 
 
322
    shrinkToNaturalHeight: function() {
 
323
        let oldWidth = this._box.width;
 
324
        let oldHeight = this._box.height;
 
325
        this._box.set_size(-1, -1);
 
326
        let [minHeight, naturalHeight] = this._box.get_preferred_height(-1);
 
327
        this._box.set_size(oldWidth, oldHeight);
 
328
 
 
329
        let batch = new Batch.ConsecutiveBatch(this,
 
330
                                               [function() {
 
331
                                                    return _smoothlyResizeActor(this._box, -1, naturalHeight);
 
332
                                                },
 
333
 
 
334
                                                function() {
 
335
                                                    this._box.set_size(-1, -1);
 
336
                                                }
 
337
                                               ]);
 
338
 
 
339
        return batch.run();
 
340
    },
 
341
 
 
342
    hideItemsExcept: function(exception) {
 
343
        let tasks = [];
 
344
 
 
345
        for (let userName in this._items) {
 
346
            let item = this._items[userName];
 
347
 
 
348
            item.actor.can_focus = false;
 
349
            item._focusBin.width = 0;
 
350
            if (item != exception)
 
351
                tasks.push(function() {
 
352
                    return _fadeOutActor(item.actor);
 
353
                });
 
354
        }
 
355
 
 
356
        let batch = new Batch.ConsecutiveBatch(this,
 
357
                                               [function() {
 
358
                                                    return _fadeOutActor(this.actor.vscroll);
 
359
                                                },
 
360
 
 
361
                                                new Batch.ConcurrentBatch(this, tasks)
 
362
                                               ]);
 
363
 
 
364
        return batch.run();
 
365
    },
 
366
 
 
367
    hideItems: function() {
 
368
        return this.hideItemsExcept(null);
 
369
    },
 
370
 
 
371
    _getExpandedHeight: function() {
 
372
        let hiddenActors = [];
 
373
        for (let userName in this._items) {
 
374
            let item = this._items[userName];
 
375
            if (!item.actor.visible) {
 
376
                item.actor.show();
 
377
                hiddenActors.push(item.actor);
 
378
            }
 
379
        }
 
380
 
 
381
        if (!this._box.visible) {
 
382
            this._box.show();
 
383
            hiddenActors.push(this._box);
 
384
        }
 
385
 
 
386
        this._box.set_size(-1, -1);
 
387
        let [minHeight, naturalHeight] = this._box.get_preferred_height(-1);
 
388
 
 
389
        for (let i = 0; i < hiddenActors.length; i++) {
 
390
            let actor = hiddenActors[i];
 
391
            actor.hide();
 
392
        }
 
393
 
 
394
        return naturalHeight;
 
395
    },
 
396
 
 
397
    showItems: function() {
 
398
        let tasks = [];
 
399
 
 
400
        for (let userName in this._items) {
 
401
            let item = this._items[userName];
 
402
            item.actor.can_focus = true;
 
403
            tasks.push(function() {
 
404
                return this._showItem(item);
 
405
            });
 
406
        }
 
407
 
 
408
        let batch = new Batch.ConsecutiveBatch(this,
 
409
                                               [function() {
 
410
                                                    this.takeOverWhitespace();
 
411
                                                },
 
412
 
 
413
                                                function() {
 
414
                                                    let fullHeight = this._getExpandedHeight();
 
415
                                                    return _smoothlyResizeActor(this._box, -1, fullHeight);
 
416
                                                },
 
417
 
 
418
                                                new Batch.ConcurrentBatch(this, tasks),
 
419
 
 
420
                                                function() {
 
421
                                                    this.actor.set_size(-1, -1);
 
422
                                                },
 
423
 
 
424
                                                function() {
 
425
                                                    return _fadeInActor(this.actor.vscroll);
 
426
                                                }]);
 
427
        return batch.run();
 
428
    },
 
429
 
 
430
    scrollToItem: function(item) {
 
431
        let box = item.actor.get_allocation_box();
 
432
 
 
433
        let adjustment = this.actor.get_vscroll_bar().get_adjustment();
 
434
 
 
435
        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
 
436
        Tweener.removeTweens(adjustment);
 
437
        Tweener.addTween (adjustment,
 
438
                          { value: value,
 
439
                            time: _SCROLL_ANIMATION_TIME,
 
440
                            transition: 'linear' });
 
441
    },
 
442
 
 
443
    jumpToItem: function(item) {
 
444
        let box = item.actor.get_allocation_box();
 
445
 
 
446
        let adjustment = this.actor.get_vscroll_bar().get_adjustment();
 
447
 
 
448
        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
 
449
 
 
450
        adjustment.set_value(value);
 
451
    },
 
452
 
 
453
    getItemFromUserName: function(userName) {
 
454
        let item = this._items[userName];
 
455
 
 
456
        if (!item)
 
457
            return null;
 
458
 
 
459
        return item;
 
460
    },
 
461
 
 
462
    addUser: function(user) {
 
463
        if (!user.is_loaded)
 
464
            return;
 
465
 
 
466
        if (user.is_system_account())
 
467
            return;
 
468
 
 
469
        let userName = user.get_user_name();
 
470
 
 
471
        if (!userName)
 
472
            return;
 
473
 
 
474
        this.removeUser(user);
 
475
 
 
476
        let item = new UserListItem(user);
 
477
        this._box.add(item.actor, { x_fill: true });
 
478
 
 
479
        this._items[userName] = item;
 
480
 
 
481
        item.connect('activate',
 
482
                     Lang.bind(this, this._onItemActivated));
 
483
 
 
484
        // Try to keep the focused item front-and-center
 
485
        item.actor.connect('key-focus-in',
 
486
                           Lang.bind(this,
 
487
                                     function() {
 
488
                                         this.scrollToItem(item);
 
489
                                         item.showFocusAnimation(0);
 
490
                                     }));
 
491
 
 
492
        this.emit('item-added', item);
 
493
    },
 
494
 
 
495
    removeUser: function(user) {
 
496
        if (!user.is_loaded)
 
497
            return;
 
498
 
 
499
        let userName = user.get_user_name();
 
500
 
 
501
        if (!userName)
 
502
            return;
 
503
 
 
504
        let item = this._items[userName];
 
505
 
 
506
        if (!item)
 
507
            return;
 
508
 
 
509
        item.actor.destroy();
 
510
        delete this._items[userName];
 
511
    }
 
512
};
 
513
Signals.addSignalMethods(UserList.prototype);
 
514
 
 
515
function SessionListItem(id, name) {
 
516
    this._init(id, name);
 
517
}
 
518
 
 
519
SessionListItem.prototype = {
 
520
    _init: function(id, name) {
 
521
        this.id = id;
 
522
 
 
523
        this.actor = new St.Button({ style_class: 'login-dialog-session-list-item',
 
524
                                     can_focus: true,
 
525
                                     reactive: true,
 
526
                                     x_fill: true,
 
527
                                     x_align: St.Align.START });
 
528
 
 
529
        this._box = new St.BoxLayout({ style_class: 'login-dialog-session-list-item-box' });
 
530
 
 
531
        this.actor.add_actor(this._box,
 
532
                             { expand: true,
 
533
                               x_fill: true,
 
534
                               y_fill: true });
 
535
        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
 
536
 
 
537
        this._dot = new St.DrawingArea({ style_class: 'login-dialog-session-list-item-dot' });
 
538
        this._dot.connect('repaint', Lang.bind(this, this._onRepaintDot));
 
539
        this._box.add_actor(this._dot);
 
540
        this.setShowDot(false);
 
541
 
 
542
        let label = new St.Label({ style_class: 'login-dialog-session-list-item-label',
 
543
                                   text: name });
 
544
 
 
545
        this._box.add_actor(label,
 
546
                            { expand: true,
 
547
                              x_fill: true,
 
548
                              y_fill: true });
 
549
    },
 
550
 
 
551
    setShowDot: function(show) {
 
552
        if (show)
 
553
            this._dot.opacity = 255;
 
554
        else
 
555
            this._dot.opacity = 0;
 
556
    },
 
557
 
 
558
    _onRepaintDot: function(area) {
 
559
        let cr = area.get_context();
 
560
        let [width, height] = area.get_surface_size();
 
561
        let color = area.get_theme_node().get_foreground_color();
 
562
 
 
563
        cr.setSourceRGBA (color.red / 255,
 
564
                          color.green / 255,
 
565
                          color.blue / 255,
 
566
                          color.alpha / 255);
 
567
        cr.arc(width / 2, height / 2, width / 3, 0, 2 * Math.PI);
 
568
        cr.fill();
 
569
    },
 
570
 
 
571
    _onClicked: function() {
 
572
        this.emit('activate');
 
573
    }
 
574
};
 
575
Signals.addSignalMethods(SessionListItem.prototype);
 
576
 
 
577
function SessionList() {
 
578
    this._init();
 
579
}
 
580
 
 
581
SessionList.prototype = {
 
582
    _init: function() {
 
583
        this.actor = new St.BoxLayout({ style_class: 'login-dialog-session-list',
 
584
                                        vertical: true});
 
585
 
 
586
        this._button = new St.Button({ style_class: 'login-dialog-session-list-button',
 
587
                                       can_focus: true,
 
588
                                       x_fill: true,
 
589
                                       y_fill: true });
 
590
        let box = new St.BoxLayout();
 
591
        this._button.add_actor(box,
 
592
                               { x_fill: true,
 
593
                                 y_fill: true,
 
594
                                 expand: true });
 
595
 
 
596
        this._triangle = new St.Label({ style_class: 'login-dialog-session-list-triangle',
 
597
                                        text: '\u25B8' });
 
598
        box.add_actor(this._triangle);
 
599
 
 
600
        let label = new St.Label({ style_class: 'login-dialog-session-list-label',
 
601
                                   text: _("Session...") });
 
602
        box.add_actor(label,
 
603
                      { x_fill: true,
 
604
                        y_fill: true,
 
605
                        expand: true });
 
606
 
 
607
        this._button.connect('clicked',
 
608
                             Lang.bind(this, this._onClicked));
 
609
        this.actor.add_actor(this._button,
 
610
                             { x_fill: true,
 
611
                               y_fill: true,
 
612
                               expand: true });
 
613
        this._scrollView = new St.ScrollView({ style_class: 'login-dialog-session-list-scroll-view'});
 
614
        this._scrollView.set_policy(Gtk.PolicyType.NEVER,
 
615
                                    Gtk.PolicyType.AUTOMATIC);
 
616
        this.actor.add_actor(this._scrollView,
 
617
                             { x_fill: true,
 
618
                               y_fill: true,
 
619
                               expand: true });
 
620
        this._itemList = new St.BoxLayout({ style_class: 'login-dialog-session-item-list',
 
621
                                            vertical: true });
 
622
        this._scrollView.add_actor(this._itemList,
 
623
                                   { x_fill: true,
 
624
                                     y_fill: true,
 
625
                                     expand: true });
 
626
        this._scrollView.hide();
 
627
        this.isOpen = false;
 
628
        this._populate();
 
629
    },
 
630
 
 
631
    open: function() {
 
632
        if (this.isOpen)
 
633
            return;
 
634
 
 
635
        this._button.add_style_pseudo_class('open');
 
636
        this._scrollView.show();
 
637
        this._triangle.set_text('\u25BE');
 
638
 
 
639
        this.isOpen = true;
 
640
    },
 
641
 
 
642
    close: function() {
 
643
        if (!this.isOpen)
 
644
            return;
 
645
 
 
646
        this._button.remove_style_pseudo_class('open');
 
647
        this._scrollView.hide();
 
648
        this._triangle.set_text('\u25B8');
 
649
 
 
650
        this.isOpen = false;
 
651
    },
 
652
 
 
653
    _onClicked: function() {
 
654
        if (!this.isOpen)
 
655
            this.open();
 
656
        else
 
657
            this.close();
 
658
    },
 
659
 
 
660
    setActiveSession: function(sessionId) {
 
661
         if (sessionId == this._activeSessionId)
 
662
             return;
 
663
 
 
664
         if (this._activeSessionId)
 
665
             this._items[this._activeSessionId].setShowDot(false);
 
666
 
 
667
         this._items[sessionId].setShowDot(true);
 
668
         this._activeSessionId = sessionId;
 
669
 
 
670
         this.emit('session-activated', this._activeSessionId);
 
671
    },
 
672
 
 
673
    _populate: function() {
 
674
        this._itemList.destroy_children();
 
675
        this._activeSessionId = null;
 
676
        this._items = {};
 
677
 
 
678
        let ids = GdmGreeter.get_session_ids();
 
679
        ids.sort();
 
680
 
 
681
        if (ids.length <= 1)
 
682
            this.actor.hide();
 
683
        else
 
684
            this.actor.show();
 
685
 
 
686
        for (let i = 0; i < ids.length; i++) {
 
687
            let [sessionName, sessionDescription] = GdmGreeter.get_session_name_and_description(ids[i]);
 
688
 
 
689
            let item = new SessionListItem(ids[i], sessionName);
 
690
            this._itemList.add_actor(item.actor,
 
691
                              { x_align: St.Align.START,
 
692
                                y_align: St.Align.START,
 
693
                                x_fill: true,
 
694
                                y_fill: true });
 
695
            this._items[ids[i]] = item;
 
696
 
 
697
            if (!this._activeSessionId)
 
698
                this.setActiveSession(ids[i]);
 
699
 
 
700
            item.connect('activate',
 
701
                         Lang.bind(this, function() {
 
702
                             this.setActiveSession(item.id);
 
703
                         }));
 
704
        }
 
705
    }
 
706
};
 
707
Signals.addSignalMethods(SessionList.prototype);
 
708
 
 
709
function LoginDialog() {
 
710
    if (_loginDialog == null) {
 
711
        this._init();
 
712
        _loginDialog = this;
 
713
    }
 
714
 
 
715
    return _loginDialog;
 
716
}
 
717
 
 
718
LoginDialog.prototype = {
 
719
    __proto__: ModalDialog.ModalDialog.prototype,
 
720
 
 
721
    _init: function() {
 
722
        ModalDialog.ModalDialog.prototype._init.call(this, { shellReactive: true,
 
723
                                                             styleClass: 'login-dialog' });
 
724
        this.connect('destroy',
 
725
                     Lang.bind(this, this._onDestroy));
 
726
        this.connect('opened',
 
727
                     Lang.bind(this, this._onOpened));
 
728
 
 
729
        this._userManager = AccountsService.UserManager.get_default()
 
730
        this._greeterClient = new GdmGreeter.Client();
 
731
 
 
732
        this._greeterClient.open_connection();
 
733
 
 
734
        this._greeterClient.call_start_conversation('gdm-password');
 
735
 
 
736
        this._greeterClient.connect('reset',
 
737
                                    Lang.bind(this, this._onReset));
 
738
        this._greeterClient.connect('default-session-changed',
 
739
                                    Lang.bind(this, this._onDefaultSessionChanged));
 
740
        this._greeterClient.connect('info',
 
741
                                    Lang.bind(this, this._onInfo));
 
742
        this._greeterClient.connect('problem',
 
743
                                    Lang.bind(this, this._onProblem));
 
744
        this._greeterClient.connect('info-query',
 
745
                                    Lang.bind(this, this._onInfoQuery));
 
746
        this._greeterClient.connect('secret-info-query',
 
747
                                    Lang.bind(this, this._onSecretInfoQuery));
 
748
        this._greeterClient.connect('session-opened',
 
749
                                    Lang.bind(this, this._onSessionOpened));
 
750
        this._greeterClient.connect('timed-login-requested',
 
751
                                    Lang.bind(this, this._onTimedLoginRequested));
 
752
        this._greeterClient.connect('authentication-failed',
 
753
                                    Lang.bind(this, this._onAuthenticationFailed));
 
754
        this._greeterClient.connect('conversation-stopped',
 
755
                                    Lang.bind(this, this._onConversationStopped));
 
756
 
 
757
        this._titleLabel = new St.Label({ style_class: 'login-dialog-title',
 
758
                                          text: _("Sign In") });
 
759
 
 
760
        this.contentLayout.add(this._titleLabel,
 
761
                              { y_fill: false,
 
762
                                y_align: St.Align.START });
 
763
 
 
764
        let mainContentBox = new St.BoxLayout({ vertical: false });
 
765
        this.contentLayout.add(mainContentBox,
 
766
                               { expand: true,
 
767
                                 x_fill: true,
 
768
                                 y_fill: false });
 
769
 
 
770
        this._userList = new UserList();
 
771
        mainContentBox.add(this._userList.actor,
 
772
                           { expand: true,
 
773
                             x_fill: true,
 
774
                             y_fill: true });
 
775
 
 
776
        this.setInitialKeyFocus(this._userList.actor);
 
777
 
 
778
        this._promptBox = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
 
779
                                             vertical: true });
 
780
        mainContentBox.add(this._promptBox,
 
781
                           { expand: true,
 
782
                             x_fill: true,
 
783
                             y_fill: true,
 
784
                             x_align: St.Align.START });
 
785
        this._promptLabel = new St.Label({ style_class: 'login-dialog-prompt-label' });
 
786
 
 
787
        this._mainContentBox = mainContentBox;
 
788
 
 
789
        this._promptBox.add(this._promptLabel,
 
790
                            { expand: true,
 
791
                              x_fill: true,
 
792
                              y_fill: true,
 
793
                              x_align: St.Align.START });
 
794
        this._promptEntry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
 
795
                                           can_focus: true });
 
796
        this._promptBox.add(this._promptEntry,
 
797
                            { expand: true,
 
798
                              x_fill: true,
 
799
                              y_fill: false,
 
800
                              x_align: St.Align.START });
 
801
 
 
802
        this._sessionList = new SessionList();
 
803
        this._sessionList.connect('session-activated',
 
804
                                  Lang.bind(this, function(list, sessionId) {
 
805
                                                this._greeterClient.call_select_session (sessionId);
 
806
                                            }));
 
807
 
 
808
        this._promptBox.add(this._sessionList.actor,
 
809
                               { expand: true,
 
810
                                 x_fill: true,
 
811
                                 y_fill: true,
 
812
                                 x_align: St.Align.START,
 
813
                                 y_align: St.Align.START});
 
814
        this._promptBox.hide();
 
815
 
 
816
        let notListedLabel = new St.Label({ text: _("Not listed?"),
 
817
                                            style_class: 'login-dialog-not-listed-label' });
 
818
        this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
 
819
                                                can_focus: true,
 
820
                                                child: notListedLabel,
 
821
                                                reactive: true,
 
822
                                                x_align: St.Align.START,
 
823
                                                x_fill: true });
 
824
 
 
825
        this._notListedButton.connect('clicked', Lang.bind(this, this._onNotListedClicked));
 
826
 
 
827
        this.contentLayout.add(this._notListedButton,
 
828
                               { expand: false,
 
829
                                 x_align: St.Align.START,
 
830
                                 x_fill: true });
 
831
 
 
832
        if (!this._userManager.is_loaded)
 
833
            this._userManagerLoadedId = this._userManager.connect('notify::is-loaded',
 
834
                                                                  Lang.bind(this, function() {
 
835
                                                                      if (this._userManager.is_loaded) {
 
836
                                                                          this._loadUserList();
 
837
                                                                          this._userManager.disconnect(this._userManagerLoadedId);
 
838
                                                                          this._userManagerLoadedId = 0;
 
839
                                                                      }
 
840
                                                                  }));
 
841
        else
 
842
            this._loadUserList();
 
843
 
 
844
        this._userList.connect('activate',
 
845
                               Lang.bind(this, function(userList, item) {
 
846
                                   this._onUserListActivated(item);
 
847
                               }));
 
848
 
 
849
    },
 
850
 
 
851
    _onReset: function(client, serviceName) {
 
852
        this._greeterClient.call_start_conversation('gdm-password');
 
853
 
 
854
        let tasks = [this._hidePrompt,
 
855
 
 
856
                     new Batch.ConcurrentBatch(this, [this._fadeInTitleLabel,
 
857
                                                      this._fadeInNotListedButton]),
 
858
 
 
859
                     function() {
 
860
                         this._sessionList.close();
 
861
                         this._userList.actor.show();
 
862
                         this._userList.actor.opacity = 255;
 
863
                         return this._userList.showItems();
 
864
                     },
 
865
 
 
866
                     function() {
 
867
                         this._userList.actor.reactive = true;
 
868
                         this._userList.actor.grab_key_focus();
 
869
                     }];
 
870
 
 
871
        this._user = null;
 
872
 
 
873
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
874
        batch.run();
 
875
    },
 
876
 
 
877
    _onDefaultSessionChanged: function(client, sessionId) {
 
878
        this._sessionList.setActiveSession(sessionId);
 
879
    },
 
880
 
 
881
    _onInfo: function(client, serviceName, info) {
 
882
        Main.notifyError(info);
 
883
    },
 
884
 
 
885
    _onProblem: function(client, serviceName, problem) {
 
886
        Main.notifyError(problem);
 
887
    },
 
888
 
 
889
    _onCancel: function(client) {
 
890
        this._greeterClient.call_cancel();
 
891
    },
 
892
 
 
893
    _fadeInPrompt: function() {
 
894
        let tasks = [function() {
 
895
                         return _fadeInActor(this._promptLabel);
 
896
                     },
 
897
 
 
898
                     function() {
 
899
                         return _fadeInActor(this._promptEntry);
 
900
                     },
 
901
 
 
902
                     function() {
 
903
                         return _fadeInActor(this._promptBox);
 
904
                     },
 
905
 
 
906
                     function() {
 
907
                         if (this._user && this._user.is_logged_in())
 
908
                             return null;
 
909
 
 
910
                         return _fadeInActor(this._sessionList.actor);
 
911
                     },
 
912
 
 
913
                     function() {
 
914
                         this._promptEntry.grab_key_focus();
 
915
                     }];
 
916
 
 
917
        this._sessionList.actor.hide();
 
918
        let batch = new Batch.ConcurrentBatch(this, tasks);
 
919
        return batch.run();
 
920
    },
 
921
 
 
922
    _showPrompt: function() {
 
923
        let hold = new Batch.Hold();
 
924
 
 
925
        let buttons = [{ action: Lang.bind(this, this._onCancel),
 
926
                         label: _("Cancel"),
 
927
                         key: Clutter.Escape },
 
928
                       { action: Lang.bind(this, function() {
 
929
                                     hold.release();
 
930
                                 }),
 
931
                         label: _("Sign In") }];
 
932
 
 
933
        this._promptEntryActivateCallbackId = this._promptEntry.clutter_text.connect('activate',
 
934
                                                                                     Lang.bind(this, function() {
 
935
                                                                                         hold.release();
 
936
                                                                                     }));
 
937
        hold.connect('release', Lang.bind(this, function() {
 
938
                         this._promptEntry.clutter_text.disconnect(this._promptEntryActivateCallbackId);
 
939
                         this._promptEntryActivateCallbackId = null;
 
940
                     }));
 
941
 
 
942
        let tasks = [function() {
 
943
                         return this._fadeInPrompt();
 
944
                     },
 
945
 
 
946
                     function() {
 
947
                         this.setButtons(buttons);
 
948
                     },
 
949
 
 
950
                     hold];
 
951
 
 
952
        let batch = new Batch.ConcurrentBatch(this, tasks);
 
953
 
 
954
        return batch.run();
 
955
    },
 
956
 
 
957
    _hidePrompt: function() {
 
958
        if (this._promptEntryActivateCallbackId) {
 
959
            this._promptEntry.clutter_text.disconnect(this._promptEntryActivateCallbackId);
 
960
            this._promptEntryActivateCallbackId = null;
 
961
        }
 
962
 
 
963
        this.setButtons([]);
 
964
 
 
965
        let tasks = [function() {
 
966
                         return _fadeOutActor(this._promptBox);
 
967
                     },
 
968
 
 
969
                     function() {
 
970
                         this._promptEntry.set_text('');
 
971
                     }];
 
972
 
 
973
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
974
 
 
975
        return batch.run();
 
976
    },
 
977
 
 
978
    _askQuestion: function(serviceName, question) {
 
979
        this._promptLabel.set_text(question);
 
980
 
 
981
        let tasks = [this._showPrompt,
 
982
 
 
983
                     function() {
 
984
                         let _text = this._promptEntry.get_text();
 
985
                         this._promptEntry.set_text('');
 
986
                         this._greeterClient.call_answer_query(serviceName, _text);
 
987
                     }];
 
988
 
 
989
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
990
        return batch.run();
 
991
    },
 
992
    _onInfoQuery: function(client, serviceName, question) {
 
993
        this._promptEntry.set_text('');
 
994
        this._promptEntry.clutter_text.set_password_char('');
 
995
        this._askQuestion(serviceName, question);
 
996
    },
 
997
 
 
998
    _onSecretInfoQuery: function(client, serviceName, secretQuestion) {
 
999
        this._promptEntry.set_text('');
 
1000
        this._promptEntry.clutter_text.set_password_char('\u25cf');
 
1001
        this._askQuestion(serviceName, secretQuestion);
 
1002
    },
 
1003
 
 
1004
    _onSessionOpened: function(client, serviceName) {
 
1005
        this._greeterClient.call_start_session_when_ready(serviceName, true);
 
1006
    },
 
1007
 
 
1008
    _waitForItemForUser: function(userName) {
 
1009
        let item = this._userList.getItemFromUserName(userName);
 
1010
 
 
1011
        if (item)
 
1012
          return null;
 
1013
 
 
1014
        let hold = new Batch.Hold();
 
1015
        let signalId = this._userList.connect('item-added',
 
1016
                                              Lang.bind(this, function() {
 
1017
                                                  let item = this._userList.getItemFromUserName(userName);
 
1018
 
 
1019
                                                  if (item)
 
1020
                                                      hold.release();
 
1021
                                              }));
 
1022
 
 
1023
        hold.connect('release', Lang.bind(this, function() {
 
1024
                         this._userList.disconnect(signalId);
 
1025
                     }));
 
1026
 
 
1027
        return hold;
 
1028
    },
 
1029
 
 
1030
    _showTimedLoginAnimation: function() {
 
1031
        this._timedLoginItem.actor.grab_key_focus();
 
1032
        return this._timedLoginItem.showFocusAnimation(this._timedLoginAnimationTime);
 
1033
    },
 
1034
 
 
1035
    _blockTimedLoginUntilIdle: function() {
 
1036
        // This blocks timed login from starting until a few
 
1037
        // seconds after the user stops interacting with the
 
1038
        // login screen.
 
1039
        //
 
1040
        // We skip this step if the timed login delay is very
 
1041
        // short.
 
1042
        if ((this._timedLoginDelay - _TIMED_LOGIN_IDLE_THRESHOLD) <= 0)
 
1043
          return null;
 
1044
 
 
1045
        let hold = new Batch.Hold();
 
1046
 
 
1047
        this._timedLoginIdleTimeOutId = Mainloop.timeout_add_seconds(_TIMED_LOGIN_IDLE_THRESHOLD,
 
1048
                                                                     function() {
 
1049
                                                                         this._timedLoginAnimationTime -= _TIMED_LOGIN_IDLE_THRESHOLD;
 
1050
                                                                         hold.release();
 
1051
                                                                     });
 
1052
        return hold;
 
1053
    },
 
1054
 
 
1055
    _startTimedLogin: function(userName, delay) {
 
1056
        this._timedLoginItem = null;
 
1057
        this._timedLoginDelay = delay;
 
1058
        this._timedLoginAnimationTime = delay;
 
1059
 
 
1060
        let tasks = [function() {
 
1061
                         return this._waitForItemForUser(userName);
 
1062
                     },
 
1063
 
 
1064
                     function() {
 
1065
                         this._timedLoginItem = this._userList.getItemFromUserName(userName);
 
1066
                     },
 
1067
 
 
1068
                     function() {
 
1069
                         // If we're just starting out, start on the right
 
1070
                         // item.
 
1071
                         if (!this.is_loaded) {
 
1072
                             this._userList.jumpToItem(this._timedLoginItem);
 
1073
                             this._timedLoginItem.showFocusAnimation(0);
 
1074
                         }
 
1075
                     },
 
1076
 
 
1077
                     this._blockTimedLoginUntilIdle,
 
1078
 
 
1079
                     function() {
 
1080
                         this._userList.scrollToItem(this._timedLoginItem);
 
1081
                     },
 
1082
 
 
1083
                     this._showTimedLoginAnimation,
 
1084
 
 
1085
                     function() {
 
1086
                         this._timedLoginBatch = null;
 
1087
                         this._greeterClient.call_begin_auto_login(userName);
 
1088
                     }];
 
1089
 
 
1090
        this._timedLoginBatch = new Batch.ConsecutiveBatch(this, tasks);
 
1091
 
 
1092
        return this._timedLoginBatch.run();
 
1093
    },
 
1094
 
 
1095
    _resetTimedLogin: function() {
 
1096
        if (this._timedLoginBatch) {
 
1097
            this._timedLoginBatch.cancel();
 
1098
            this._timedLoginBatch = null;
 
1099
        }
 
1100
 
 
1101
        let userName = this._timedLoginItem.user.get_user_name();
 
1102
 
 
1103
        if (userName)
 
1104
            this._startTimedLogin(userName, this._timedLoginDelay);
 
1105
    },
 
1106
 
 
1107
    _onTimedLoginRequested: function(client, userName, seconds) {
 
1108
        this._startTimedLogin(userName, seconds);
 
1109
 
 
1110
        global.stage.connect('captured-event',
 
1111
                             Lang.bind(this, function(actor, event) {
 
1112
                                if (this._timedLoginDelay == undefined)
 
1113
                                    return false;
 
1114
 
 
1115
                                if (event.type() == Clutter.EventType.KEY_PRESS ||
 
1116
                                    event.type() == Clutter.EventType.BUTTON_PRESS) {
 
1117
                                    if (this._timedLoginBatch) {
 
1118
                                        this._timedLoginBatch.cancel();
 
1119
                                        this._timedLoginBatch = null;
 
1120
                                    }
 
1121
                                } else if (event.type() == Clutter.EventType.KEY_RELEASE ||
 
1122
                                           event.type() == Clutter.EventType.BUTTON_RELEASE) {
 
1123
                                    this._resetTimedLogin();
 
1124
                                }
 
1125
 
 
1126
                                return false;
 
1127
                             }));
 
1128
    },
 
1129
 
 
1130
    _onAuthenticationFailed: function(client) {
 
1131
        this._greeterClient.call_cancel();
 
1132
    },
 
1133
 
 
1134
    _onConversationStopped: function(client, serviceName) {
 
1135
        this._greeterClient.call_cancel();
 
1136
    },
 
1137
 
 
1138
    _onNotListedClicked: function(user) {
 
1139
        let tasks = [function() {
 
1140
                         return this._userList.hideItems();
 
1141
                     },
 
1142
 
 
1143
                     function() {
 
1144
                         return this._userList.giveUpWhitespace();
 
1145
                     },
 
1146
 
 
1147
                     function() {
 
1148
                         this._userList.actor.hide();
 
1149
                     },
 
1150
 
 
1151
                     new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
 
1152
                                                      this._fadeOutNotListedButton]),
 
1153
 
 
1154
                     function() {
 
1155
                         this._greeterClient.call_begin_verification('gdm-password');
 
1156
                     }];
 
1157
 
 
1158
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
1159
        batch.run();
 
1160
    },
 
1161
 
 
1162
    _fadeInTitleLabel: function() {
 
1163
        return _fadeInActor(this._titleLabel);
 
1164
    },
 
1165
 
 
1166
    _fadeOutTitleLabel: function() {
 
1167
        return _fadeOutActor(this._titleLabel);
 
1168
    },
 
1169
 
 
1170
    _fadeInNotListedButton: function() {
 
1171
        return _fadeInActor(this._notListedButton);
 
1172
    },
 
1173
 
 
1174
    _fadeOutNotListedButton: function() {
 
1175
        return _fadeOutActor(this._notListedButton);
 
1176
    },
 
1177
 
 
1178
    _onUserListActivated: function(activatedItem) {
 
1179
        let tasks = [function() {
 
1180
                         this._userList.actor.reactive = false;
 
1181
                         return this._userList.pinInPlace();
 
1182
                     },
 
1183
 
 
1184
                     function() {
 
1185
                         return this._userList.hideItemsExcept(activatedItem);
 
1186
                     },
 
1187
 
 
1188
                     function() {
 
1189
                         return this._userList.giveUpWhitespace();
 
1190
                     },
 
1191
 
 
1192
                     function() {
 
1193
                         return activatedItem.fadeOutName();
 
1194
                     },
 
1195
 
 
1196
                     new Batch.ConcurrentBatch(this, [this._fadeOutTitleLabel,
 
1197
                                                      this._fadeOutNotListedButton]),
 
1198
 
 
1199
                     function() {
 
1200
                         return this._userList.shrinkToNaturalHeight();
 
1201
                     },
 
1202
 
 
1203
                     function() {
 
1204
                         let userName = activatedItem.user.get_user_name();
 
1205
                         this._greeterClient.call_begin_verification_for_user('gdm-password',
 
1206
                                                                              userName);
 
1207
                     }];
 
1208
 
 
1209
        this._user = activatedItem.user;
 
1210
 
 
1211
        let batch = new Batch.ConsecutiveBatch(this, tasks);
 
1212
        batch.run();
 
1213
    },
 
1214
 
 
1215
    _onDestroy: function() {
 
1216
        if (this._userManagerLoadedId) {
 
1217
            this._userManager.disconnect(this._userManagerLoadedId);
 
1218
            this._userManagerLoadedId = 0;
 
1219
        }
 
1220
    },
 
1221
 
 
1222
    _loadUserList: function() {
 
1223
        let users = this._userManager.list_users();
 
1224
 
 
1225
        for (let i = 0; i < users.length; i++) {
 
1226
            this._userList.addUser(users[i]);
 
1227
        }
 
1228
 
 
1229
        this._userManager.connect('user-added',
 
1230
                                  Lang.bind(this, function(userManager, user) {
 
1231
                                      this._userList.addUser(user);
 
1232
                                  }));
 
1233
 
 
1234
        this._userManager.connect('user-removed',
 
1235
                                  Lang.bind(this, function(userManager, user) {
 
1236
                                      this._userList.removeUser(user);
 
1237
                                  }));
 
1238
 
 
1239
        // emitted in idle so caller doesn't have to explicitly check if
 
1240
        // it's loaded immediately after construction
 
1241
        // (since there's no way the caller could be listening for
 
1242
        // 'loaded' yet)
 
1243
        Mainloop.idle_add(Lang.bind(this, function() {
 
1244
            this.emit('loaded');
 
1245
            this.is_loaded = true;
 
1246
        }));
 
1247
    },
 
1248
 
 
1249
    _onOpened: function() {
 
1250
        Main.ctrlAltTabManager.addGroup(this._mainContentBox,
 
1251
                                        _("Login Window"),
 
1252
                                        'dialog-password',
 
1253
                                        { sortGroup: CtrlAltTab.SortGroup.MIDDLE });
 
1254
 
 
1255
    },
 
1256
 
 
1257
    close: function() {
 
1258
        ModalDialog.ModalDialog.prototype.close.call(this);
 
1259
 
 
1260
        Main.ctrlAltTabManager.removeGroup(this._group);
 
1261
    }
 
1262
};