904ceba6b2
Start implementing the lock screen design by adding a big white clock in the middle. https://bugzilla.gnome.org/show_bug.cgi?id=619955
362 lines
12 KiB
JavaScript
362 lines
12 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
|
const Lang = imports.lang;
|
|
const Meta = imports.gi.Meta;
|
|
const Signals = imports.signals;
|
|
const St = imports.gi.St;
|
|
|
|
const GnomeSession = imports.misc.gnomeSession;
|
|
const Lightbox = imports.ui.lightbox;
|
|
const Main = imports.ui.main;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
|
|
const LOCK_ENABLED_KEY = 'lock-enabled';
|
|
|
|
const CURTAIN_SLIDE_TIME = 1.2;
|
|
// fraction of screen height the arrow must reach before completing
|
|
// the slide up automatically
|
|
const ARROW_DRAG_TRESHOLD = 0.4;
|
|
|
|
const Clock = new Lang.Class({
|
|
Name: 'ScreenShieldClock',
|
|
|
|
CLOCK_FORMAT_KEY: 'clock-format',
|
|
CLOCK_SHOW_SECONDS_KEY: 'clock-show-seconds',
|
|
|
|
_init: function() {
|
|
this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock',
|
|
vertical: true });
|
|
|
|
this._time = new St.Label({ style_class: 'screen-shield-clock-time' });
|
|
this._date = new St.Label({ style_class: 'screen-shield-clock-date' });
|
|
|
|
this.actor.add(this._time, { x_align: St.Align.MIDDLE });
|
|
this.actor.add(this._date, { x_align: St.Align.MIDDLE });
|
|
|
|
this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
|
|
this._wallClock.connect('notify::clock', Lang.bind(this, this._updateClock));
|
|
|
|
this._updateClock();
|
|
},
|
|
|
|
_updateClock: function() {
|
|
this._time.text = this._wallClock.clock;
|
|
|
|
let date = new Date();
|
|
/* Translators: This is a time format for a date in
|
|
long format */
|
|
this._date.text = date.toLocaleFormat(_("%A, %B %d"));
|
|
},
|
|
|
|
destroy: function() {
|
|
this.actor.destroy();
|
|
this._wallClock.run_dispose();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* To test screen shield, make sure to kill gnome-screensaver.
|
|
*
|
|
* If you are setting org.gnome.desktop.session.idle-delay directly in dconf,
|
|
* rather than through System Settings, you also need to set
|
|
* org.gnome.settings-daemon.plugins.power.sleep-display-ac and
|
|
* org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value.
|
|
* This will ensure that the screen blanks at the right time when it fades out.
|
|
* https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependance.
|
|
*/
|
|
const ScreenShield = new Lang.Class({
|
|
Name: 'ScreenShield',
|
|
|
|
_init: function() {
|
|
this.actor = Main.layoutManager.screenShieldGroup;
|
|
|
|
this._lockScreenGroup = new St.Widget({ x_expand: true,
|
|
y_expand: true,
|
|
reactive: true,
|
|
can_focus: true,
|
|
layout_manager: new Clutter.BinLayout()
|
|
});
|
|
this._lockScreenGroup.connect('key-release-event',
|
|
Lang.bind(this, this._onLockScreenKeyRelease));
|
|
|
|
this._background = Meta.BackgroundActor.new_for_screen(global.screen);
|
|
this._lockScreenGroup.add_actor(this._background);
|
|
|
|
// FIXME: build the rest of the lock screen here
|
|
|
|
this._arrow = new St.DrawingArea({ style_class: 'arrow',
|
|
reactive: true,
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.END,
|
|
// HACK: without these, ClutterBinLayout
|
|
// ignores alignment properties on the actor
|
|
x_expand: true,
|
|
y_expand: true
|
|
});
|
|
this._arrow.connect('repaint', Lang.bind(this, this._drawArrow));
|
|
this._lockScreenGroup.add_actor(this._arrow);
|
|
|
|
let action = new Clutter.DragAction({ drag_axis: Clutter.DragAxis.Y_AXIS });
|
|
action.connect('drag-begin', Lang.bind(this, this._onDragBegin));
|
|
action.connect('drag-end', Lang.bind(this, this._onDragEnd));
|
|
this._lockScreenGroup.add_action(action);
|
|
|
|
this._lockDialogGroup = new St.Widget({ x_expand: true,
|
|
y_expand: true });
|
|
|
|
this.actor.add_actor(this._lockDialogGroup);
|
|
this.actor.add_actor(this._lockScreenGroup);
|
|
|
|
this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) {
|
|
if (error) {
|
|
logError(error, 'Error while reading gnome-session presence');
|
|
return;
|
|
}
|
|
|
|
this._onStatusChanged(proxy.status);
|
|
}));
|
|
this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
|
|
this._onStatusChanged(status);
|
|
}));
|
|
|
|
this._settings = new Gio.Settings({ schema: SCREENSAVER_SCHEMA });
|
|
|
|
this._isModal = false;
|
|
this._isLocked = false;
|
|
this._hasLockScreen = false;
|
|
|
|
this._lightbox = new Lightbox.Lightbox(Main.uiGroup,
|
|
{ inhibitEvents: true, fadeInTime: 10, fadeFactor: 1 });
|
|
},
|
|
|
|
_onLockScreenKeyRelease: function(actor, event) {
|
|
if (event.get_key_symbol() == Clutter.KEY_Escape) {
|
|
this._showUnlockDialog(true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
_drawArrow: function() {
|
|
let cr = this._arrow.get_context();
|
|
let [w, h] = this._arrow.get_surface_size();
|
|
let node = this._arrow.get_theme_node();
|
|
|
|
Clutter.cairo_set_source_color(cr, node.get_foreground_color());
|
|
|
|
cr.moveTo(0, h);
|
|
cr.lineTo(w/2, 0);
|
|
cr.lineTo(w, h);
|
|
cr.fill();
|
|
},
|
|
|
|
_onDragBegin: function() {
|
|
Tweener.removeTweens(this._lockScreenGroup);
|
|
},
|
|
|
|
_onDragEnd: function(action, actor, eventX, eventY, modifiers) {
|
|
if (this._lockScreenGroup.y < -(ARROW_DRAG_TRESHOLD * global.stage.height)) {
|
|
// Complete motion automatically
|
|
this._showUnlockDialog(true);
|
|
} else {
|
|
// restore the lock screen to its original place
|
|
// try to use the same speed as the normal animation
|
|
let h = global.stage.height;
|
|
let time = CURTAIN_SLIDE_TIME * (-this._lockScreenGroup.y) / h;
|
|
Tweener.removeTweens(this._lockScreenGroup);
|
|
Tweener.addTween(this._lockScreenGroup,
|
|
{ y: 0,
|
|
time: time,
|
|
transition: 'linear',
|
|
onComplete: function() {
|
|
this.fixed_position_set = false;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
_onStatusChanged: function(status) {
|
|
if (status == GnomeSession.PresenceStatus.IDLE) {
|
|
if (this._dialog) {
|
|
this._dialog.cancel();
|
|
if (!this._keepDialog) {
|
|
this._dialog = null;
|
|
}
|
|
}
|
|
|
|
if (!this._isModal) {
|
|
Main.pushModal(this.actor);
|
|
this._isModal = true;
|
|
}
|
|
|
|
if (!this._isLocked)
|
|
this._lightbox.show();
|
|
} else {
|
|
let lightboxWasShown = this._lightbox.shown;
|
|
this._lightbox.hide();
|
|
|
|
let shouldLock = lightboxWasShown && this._settings.get_boolean(LOCK_ENABLED_KEY);
|
|
if (shouldLock || this._isLocked) {
|
|
this.lock();
|
|
} else if (this._isModal) {
|
|
this.unlock();
|
|
}
|
|
}
|
|
},
|
|
|
|
showDialog: function() {
|
|
this.lock();
|
|
this._showUnlockDialog(false);
|
|
},
|
|
|
|
_showUnlockDialog: function(animate) {
|
|
if (animate) {
|
|
// Tween the lock screen out of screen
|
|
// try to use the same speed regardless of original position
|
|
let h = global.stage.height;
|
|
let time = CURTAIN_SLIDE_TIME * (h + this._lockScreenGroup.y) / h;
|
|
Tweener.removeTweens(this._lockScreenGroup);
|
|
Tweener.addTween(this._lockScreenGroup,
|
|
{ y: -h,
|
|
time: time,
|
|
transition: 'linear',
|
|
onComplete: Lang.bind(this, this._hideLockScreen),
|
|
});
|
|
} else {
|
|
this._hideLockScreen();
|
|
}
|
|
|
|
if (!this._dialog) {
|
|
[this._dialog, this._keepDialog] = Main.sessionMode.createUnlockDialog(this._lockDialogGroup);
|
|
if (!this._dialog) {
|
|
// This session mode has no locking capabilities
|
|
this.unlock();
|
|
return;
|
|
}
|
|
|
|
this._dialog.connect('loaded', Lang.bind(this, function() {
|
|
if (!this._dialog.open()) {
|
|
log('Could not open login dialog: failed to acquire grab');
|
|
this.unlock();
|
|
}
|
|
}));
|
|
|
|
this._dialog.connect('failed', Lang.bind(this, this._onUnlockFailed));
|
|
this._dialog.connect('unlocked', Lang.bind(this, this._onUnlockSucceded));
|
|
}
|
|
|
|
if (this._keepDialog) {
|
|
// Notify the other components that even though we are showing the
|
|
// screenshield, we're not in a locked state
|
|
// (this happens for the gdm greeter)
|
|
|
|
this._isLocked = false;
|
|
this.emit('lock-status-changed', false);
|
|
}
|
|
},
|
|
|
|
_onUnlockFailed: function() {
|
|
this._dialog.destroy();
|
|
this._dialog = null;
|
|
|
|
this._resetLockScreen();
|
|
},
|
|
|
|
_onUnlockSucceded: function() {
|
|
this.unlock();
|
|
},
|
|
|
|
_hideLockScreen: function() {
|
|
this._arrow.hide();
|
|
this._lockScreenGroup.hide();
|
|
},
|
|
|
|
_resetLockScreen: function() {
|
|
this._lockScreenGroup.fixed_position_set = false;
|
|
this._lockScreenGroup.show();
|
|
this._arrow.show();
|
|
|
|
this._lockScreenGroup.grab_key_focus();
|
|
},
|
|
|
|
// Some of the actors in the lock screen are heavy in
|
|
// resources, so we only create them when needed
|
|
_prepareLockScreen: function() {
|
|
this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
x_expand: true,
|
|
y_expand: true,
|
|
vertical: true });
|
|
this._clock = new Clock();
|
|
this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
|
|
y_fill: true });
|
|
|
|
this._lockScreenGroup.add_actor(this._lockScreenContentsBox);
|
|
|
|
this._hasLockScreen = true;
|
|
},
|
|
|
|
_clearLockScreen: function() {
|
|
this._clock.destroy();
|
|
this._clock = null;
|
|
|
|
this._lockScreenContentsBox.destroy();
|
|
|
|
this._hasLockScreen = false;
|
|
},
|
|
|
|
get locked() {
|
|
return this._isLocked;
|
|
},
|
|
|
|
unlock: function() {
|
|
if (this._hasLockScreen)
|
|
this._clearLockScreen();
|
|
|
|
if (this._keepDialog) {
|
|
// The dialog must be kept alive,
|
|
// so immediately go back to it
|
|
// This will also reset _isLocked
|
|
this._showUnlockDialog(false);
|
|
return;
|
|
}
|
|
|
|
if (this._dialog) {
|
|
this._dialog.destroy();
|
|
this._dialog = null;
|
|
}
|
|
|
|
this._lightbox.hide();
|
|
|
|
Main.popModal(this.actor);
|
|
this.actor.hide();
|
|
|
|
this._isModal = false;
|
|
this._isLocked = false;
|
|
|
|
this.emit('lock-status-changed', false);
|
|
},
|
|
|
|
lock: function() {
|
|
if (!this._hasLockScreen)
|
|
this._prepareLockScreen();
|
|
|
|
if (!this._isModal) {
|
|
Main.pushModal(this.actor);
|
|
this._isModal = true;
|
|
}
|
|
|
|
this._isLocked = true;
|
|
this.actor.show();
|
|
this._resetLockScreen();
|
|
|
|
this.emit('lock-status-changed', true);
|
|
},
|
|
});
|
|
Signals.addSignalMethods(ScreenShield.prototype);
|