Compare commits

...

23 Commits

Author SHA1 Message Date
Georges Basile Stavracas Neto
6fd0aafada authPrompt: Wiggle on failure
WIP
2019-10-15 21:34:35 +02:00
Georges Basile Stavracas Neto
e5091ff17c environment: Parse repeat-count and auto-reverse
Those are two useful ClutterTimeline properties and
will be needed for wiggling the search entry when
failing the password.

Add support for passing repeat-count and auto-reverse
to ClutterActor.ease and ClutterActor.ease_property.
2019-10-15 21:32:21 +02:00
Georges Basile Stavracas Neto
22534c4c64 unlockDialog: Show auth prompt on tap 2019-10-15 15:39:07 +02:00
Georges Basile Stavracas Neto
cd1c45731d fixup! sessonMode: Remove 'lock-screen' mode 2019-10-15 15:38:56 +02:00
Georges Basile Stavracas Neto
5380b06eb5 fixup! unlockDialog: Add UnlockDialogLayout 2019-10-15 15:37:46 +02:00
Georges Basile Stavracas Neto
be714f7401 fixup! screenShield: Blur background 2019-10-14 18:29:31 +02:00
Georges Basile Stavracas Neto
e71c249ea5 authPrompt: Refine entry width
The current entry has a hardcoded size of 320px,
which doesn't really fit the lock screen mockups.

Remove this hardcoded size from AuthPrompt entries
and make them horizontally expand.
2019-10-12 21:33:41 -03:00
Georges Basile Stavracas Neto
5b6deff977 authPrompt: Iconize the cancel button
Replace the "Cancel" label in the cancel button
by an arrow icon, and adjust the theme to make
it circular.
2019-10-12 21:12:45 -03:00
Georges Basile Stavracas Neto
6cd0c3f965 authPrompt: Move cancel button and cog to a single row
Move the cancel button and the user well actors (the cog
menu and the spinner) to a single row.

Ultimately the user well will be splitted out into a different
place, but moving it now at least gives some visual consistency.
2019-10-12 17:58:55 -03:00
Georges Basile Stavracas Neto
d529e222d7 unlockDialog: Add UnlockDialogLayout
This is a specialized layout manager to implement the
UI description of the new lock screen.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
2e23a0e6b8 authPrompt: Remove next button 2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
64aaa46333 unlockDialog: Adjust notification style
I think I can flip the development table and
become a designer now -- what else is needed
beyond figuring out spacing anyway?!
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
d1f61e884d sessonMode: Remove 'lock-screen' mode
As per the latest mockups, the only session mode
related to locked screens will be the unlock dialog.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
fe3d55eb80 screenShield: Cleanup unused parameters 2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
7a90b2d908 screenShield: Remove _lockScreenGroup
Now, _lockDialogGroup will be responsible for being
the actual screenshield.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
137df4bc26 screenShield: Cleanup lock screen handling 2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
23abe4eb22 screenShield: Translate the dialog group
Instead of the screen lock group, which is now
empty and soon will be removed.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
b7feb71490 screenShield: Move notification box to UnlockDialog 2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
9aa9431a32 theme: Adjust unlock screen clock fonts
Designers plz help
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
c9e2afcf65 screenShield: Move Clock to UnlockDialog
Move the clock to UnlockDialog, as per the latest mockups.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
be5ab24e97 screenShield: Remove arrows
They're not used anywhere in the new dialog
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
caf0c8dd7d screenShield: Blur background
This is a moderately fast two-pass gaussian blur
implementation. It downscales the framebuffer by
half before applying the gaussian shader, which
cuts down rendering time quite considerably.

The blur shader takes 3 uniforms as input: the blur
radius; whether to blur vertically or horizontally;
and a brightness value.

The blur radius is treated as an integer in C land
to simplify calculations. The vertical parameter is
treated as an integer by the shader simply due to
Cogl not having proper boolean support in snippets.
At last, brightness is also added to avoid needing
to use an extra effect to achieve that.
2019-10-12 17:49:40 -03:00
Georges Basile Stavracas Neto
10a194e6ed screenShield: Move background to _screenDialogGroup 2019-10-09 19:42:40 -03:00
9 changed files with 1205 additions and 850 deletions

View File

@ -1929,6 +1929,7 @@ StScrollBar {
StEntry {
@extend %search_entry;
width: -1px;
border-radius: $button_radius;
@if $variant=='dark' {
$_gdm_entry_bg: transparentize(lighten(desaturate(#241f31, 20%), 2%), 0.5);
@ -1986,6 +1987,15 @@ StScrollBar {
}
}
}
.cancel-button {
padding: 0;
border-radius: 16px;
width: 32px;
height: 32px;
StIcon { icon-size: 16px; }
}
}
.login-dialog-logo-bin { padding: 24px 0px; }
@ -2069,42 +2079,28 @@ StScrollBar {
$_screenshield_shadow: 0px 0px 6px rgba(0, 0, 0, 0.726);
.screen-shield-arrows {
padding-bottom: 3em;
}
.screen-shield-arrows Gjs_Arrow {
color: white;
width: 80px;
height: 48px;
-arrow-thickness: 12px;
-arrow-shadow: $_screenshield_shadow;
}
.screen-shield-clock {
color: white;
text-shadow: $_screenshield_shadow;
font-weight: bold;
text-align: center;
padding-bottom: 1.5em;
padding-bottom: 2.5em;
}
.screen-shield-clock-time {
font-size: 72pt;
text-shadow: $_screenshield_shadow;
font-size: 64pt;
font-weight: 200;
padding-bottom: 24px;
font-feature-settings: "tnum";
}
.screen-shield-clock-date {
font-size: 28pt;
font-weight: normal;
font-size: 16pt;
font-weight: bold;
}
.screen-shield-notifications-container {
spacing: 6px;
width: 30em;
background-color: transparent;
max-height: 500px;
padding: 24px 0;
.summary-notification-stack-scrollview {
padding-top: 0;
padding-bottom: 0;
@ -2112,7 +2108,7 @@ $_screenshield_shadow: 0px 0px 6px rgba(0, 0, 0, 0.726);
.notification,
.screen-shield-notification-source {
padding: 12px 6px;
padding: 12px;
border: 1px solid $osd_outer_borders_color;
background-color: transparentize($osd_bg_color,0.5);
color: $osd_fg_color;

View File

@ -16,6 +16,10 @@ var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300;
var MESSAGE_FADE_OUT_ANIMATION_TIME = 500;
const WIGGLE_OFFSET = 6;
const WIGGLE_DURATION = 65;
const N_WIGGLES = 3;
var AuthPromptMode = {
UNLOCK_ONLY: 0,
UNLOCK_OR_LOG_IN: 1
@ -90,40 +94,14 @@ var AuthPrompt = class {
x_fill: false,
y_fill: true,
x_align: St.Align.START });
this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
can_focus: true });
ShellEntry.addContextMenu(this._entry, { isPassword: true, actionMode: Shell.ActionMode.NONE });
this.actor.add(this._entry,
{ expand: true,
x_fill: true,
y_fill: false,
x_align: St.Align.START });
this._entry.grab_key_focus();
this._initEntryRow();
this._message = new St.Label({ opacity: 0,
styleClass: 'login-dialog-message' });
this._message.clutter_text.line_wrap = true;
this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START });
this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
vertical: false });
this.actor.add(this._buttonBox,
{ expand: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._defaultButtonWellActor = null;
this._initButtons();
this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
this._spinner.actor.opacity = 0;
this._spinner.actor.show();
this._defaultButtonWell.add_child(this._spinner.actor);
}
_onDestroy() {
@ -131,52 +109,47 @@ var AuthPrompt = class {
this._userVerifier = null;
}
_initButtons() {
this.cancelButton = new St.Button({ style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Cancel") });
_initEntryRow() {
let mainBox = new St.BoxLayout({
style_class: 'login-dialog-button-box',
vertical: false,
});
this.actor.add_child(mainBox);
this.cancelButton = new St.Button({
style_class: 'modal-dialog-button button cancel-button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
child: new St.Icon({ icon_name: 'go-previous-symbolic' }),
});
this.cancelButton.connect('clicked', () => this.cancel());
this._buttonBox.add(this.cancelButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.START,
y_align: St.Align.END });
mainBox.add_child(this.cancelButton);
this._buttonBox.add(this._defaultButtonWell,
{ expand: true,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.MIDDLE });
this.nextButton = new St.Button({ style_class: 'modal-dialog-button button',
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
reactive: true,
can_focus: true,
label: _("Next") });
this.nextButton.connect('clicked', () => this.emit('next'));
this.nextButton.add_style_pseudo_class('default');
this._buttonBox.add(this.nextButton,
{ expand: false,
x_fill: false,
y_fill: false,
x_align: St.Align.END,
y_align: St.Align.END });
this._entry = new St.Entry({
style_class: 'login-dialog-prompt-entry',
can_focus: true,
x_expand: true,
});
ShellEntry.addContextMenu(this._entry, { isPassword: true, actionMode: Shell.ActionMode.NONE });
this._updateNextButtonSensitivity(this._entry.text.length > 0);
mainBox.add_child(this._entry);
this._entry.grab_key_focus();
this._entry.clutter_text.connect('activate', () => this.emit('next'));
this._entry.clutter_text.connect('text-changed', () => {
if (!this._userVerifier.hasPendingMessages)
this._fadeOutMessage();
});
this._updateNextButtonSensitivity(this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING);
});
this._entry.clutter_text.connect('activate', () => {
if (this.nextButton.reactive)
this.emit('next');
});
this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
this._defaultButtonWellActor = null;
mainBox.add_child(this._defaultButtonWell);
this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
this._spinner.actor.opacity = 0;
this._spinner.actor.show();
this._defaultButtonWell.add_child(this._spinner.actor);
}
_onAskQuestion(verifier, serviceName, question, passwordChar) {
@ -191,16 +164,6 @@ var AuthPrompt = class {
}
this.setPasswordChar(passwordChar);
this.setQuestion(question);
if (passwordChar) {
if (this._userVerifier.reauthenticating)
this.nextButton.label = _("Unlock");
else
this.nextButton.label = C_("button", "Sign In");
} else {
this.nextButton.label = _("Next");
}
this.updateSensitivity(true);
this.emit('prompted');
}
@ -241,6 +204,35 @@ var AuthPrompt = class {
this.updateSensitivity(canRetry);
this.setActorInDefaultButtonWell(null);
this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
this._wiggle();
}
_wiggle() {
// Accelerate before wiggling
this._entry.ease({
translation_x: -WIGGLE_OFFSET,
duration: WIGGLE_DURATION,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
// Wiggle
this._entry.ease({
translation_x: WIGGLE_OFFSET,
duration: WIGGLE_DURATION,
mode: Clutter.AnimationMode.LINEAR,
repeat_count: N_WIGGLES,
auto_reverse: true,
onComplete: () => {
// Decelerate and return to the original position
this._entry.ease({
translation_x: 0,
duration: WIGGLE_DURATION,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
});
}
});
}
});
}
_onVerificationComplete() {
@ -393,13 +385,7 @@ var AuthPrompt = class {
}
}
_updateNextButtonSensitivity(sensitive) {
this.nextButton.reactive = sensitive;
this.nextButton.can_focus = sensitive;
}
updateSensitivity(sensitive) {
this._updateNextButtonSensitivity(sensitive && (this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING));
this._entry.reactive = sensitive;
this._entry.clutter_text.editable = sensitive;
}
@ -430,7 +416,6 @@ var AuthPrompt = class {
let oldStatus = this.verificationStatus;
this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
this.cancelButton.reactive = true;
this.nextButton.label = _("Next");
this._preemptiveAnswer = null;
if (this._userVerifier)

View File

@ -105,6 +105,16 @@ function _easeActor(actor, params) {
actor.set_easing_delay(params.delay);
delete params.delay;
let repeat_count = 0;
if (params.repeat_count != undefined)
repeat_count = params.repeat_count;
delete params.repeat_count;
let auto_reverse = false;
if (params.auto_reverse != undefined)
auto_reverse = params.auto_reverse;
delete params.auto_reverse;
if (params.mode != undefined)
actor.set_easing_mode(params.mode);
delete params.mode;
@ -124,10 +134,14 @@ function _easeActor(actor, params) {
let transition = animatedProps.map(p => actor.get_transition(p))
.find(t => t !== null);
if (transition)
if (transition) {
transition.repeat_count = repeat_count;
transition.auto_reverse = auto_reverse;
transition.connect('stopped', (t, finished) => callback(finished));
else
} else {
callback(true);
}
}
function _easeActorProperty(actor, propName, target, params) {
@ -140,6 +154,16 @@ function _easeActorProperty(actor, propName, target, params) {
params.duration = adjustAnimationTime(params.duration);
let duration = Math.floor(params.duration || 0);
let repeat_count = 0;
if (params.repeat_count != undefined)
repeat_count = params.repeat_count;
delete params.repeat_count;
let auto_reverse = false;
if (params.auto_reverse != undefined)
auto_reverse = params.auto_reverse;
delete params.auto_reverse;
// Copy Clutter's behavior for implicit animations, see
// should_skip_implicit_transition()
if (actor instanceof Clutter.Actor && !actor.mapped)
@ -166,7 +190,9 @@ function _easeActorProperty(actor, propName, target, params) {
let transition = new Clutter.PropertyTransition(Object.assign({
property_name: propName,
interval: new Clutter.Interval({ value_type: pspec.value_type }),
remove_on_complete: true
remove_on_complete: true,
repeat_count: repeat_count,
auto_reverse: auto_reverse,
}, params));
actor.add_transition(propName, transition);

View File

@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { AccountsService, Clutter, Cogl, Gio, GLib,
GnomeDesktop, GObject, Meta, Shell, St } = imports.gi;
GObject, Meta, Shell, St } = imports.gi;
const Cairo = imports.cairo;
const Signals = imports.signals;
@ -16,6 +16,7 @@ const Overview = imports.ui.overview;
const MessageTray = imports.ui.messageTray;
const ShellDBus = imports.ui.shellDBus;
const SmartcardManager = imports.misc.smartcardManager;
const Params = imports.misc.params;
const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled';
@ -25,16 +26,14 @@ const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const DISABLE_LOCK_KEY = 'disable-lock-screen';
const LOCKED_STATE_STR = 'screenShield.locked';
const BLUR_BRIGHTNESS = 0.55;
const BLUR_RADIUS = 200;
// fraction of screen height the arrow must reach before completing
// the slide up automatically
var ARROW_DRAG_THRESHOLD = 0.1;
// Parameters for the arrow animation
var N_ARROWS = 3;
var ARROW_ANIMATION_TIME = 600;
var ARROW_ANIMATION_PEAK_OPACITY = 0.4;
var ARROW_IDLE_TIME = 30000; // ms
var SUMMARY_ICON_SIZE = 48;
// ScreenShield animation time
@ -46,373 +45,6 @@ var STANDARD_FADE_TIME = 10000;
var MANUAL_FADE_TIME = 300;
var CURTAIN_SLIDE_TIME = 300;
var Clock = class {
constructor() {
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', this._updateClock.bind(this));
this._updateClock();
}
_updateClock() {
this._time.text = this._wallClock.clock;
let date = new Date();
/* Translators: This is a time format for a date in
long format */
let dateFormat = Shell.util_translate_time_string(N_("%A, %B %d"));
this._date.text = date.toLocaleFormat(dateFormat);
}
destroy() {
this.actor.destroy();
this._wallClock.run_dispose();
}
};
var NotificationsBox = class {
constructor() {
this.actor = new St.BoxLayout({ vertical: true,
name: 'screenShieldNotifications',
style_class: 'screen-shield-notifications-container' });
this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START,
hscrollbar_policy: St.PolicyType.NEVER });
this._notificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-container' });
this._scrollView.add_actor(this._notificationBox);
this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START });
this._sources = new Map();
Main.messageTray.getSources().forEach(source => {
this._sourceAdded(Main.messageTray, source, true);
});
this._updateVisibility();
this._sourceAddedId = Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
}
destroy() {
if (this._sourceAddedId) {
Main.messageTray.disconnect(this._sourceAddedId);
this._sourceAddedId = 0;
}
let items = this._sources.entries();
for (let [source, obj] of items) {
this._removeSource(source, obj);
}
this.actor.destroy();
}
_updateVisibility() {
this._notificationBox.visible =
this._notificationBox.get_children().some(a => a.visible);
this.actor.visible = this._notificationBox.visible;
}
_makeNotificationCountText(count, isChat) {
if (isChat)
return ngettext("%d new message", "%d new messages", count).format(count);
else
return ngettext("%d new notification", "%d new notifications", count).format(count);
}
_makeNotificationSource(source, box) {
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
box.add(sourceActor, { y_fill: true });
let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox, { y_fill: false, y_align: St.Align.START });
let title = new St.Label({ text: source.title,
style_class: 'screen-shield-notification-label' });
textBox.add(title);
let count = source.unseenCount;
let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
style_class: 'screen-shield-notification-count-text' });
textBox.add(countLabel);
box.visible = count != 0;
return [title, countLabel];
}
_makeNotificationDetailedSource(source, box) {
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
let sourceBin = new St.Bin({ y_align: St.Align.START,
x_align: St.Align.START,
child: sourceActor });
box.add(sourceBin);
let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox, { y_fill: false, y_align: St.Align.START });
let title = new St.Label({ text: source.title,
style_class: 'screen-shield-notification-label' });
textBox.add(title);
let visible = false;
for (let i = 0; i < source.notifications.length; i++) {
let n = source.notifications[i];
if (n.acknowledged)
continue;
let body = '';
if (n.bannerBodyText) {
body = n.bannerBodyMarkup
? n.bannerBodyText
: GLib.markup_escape_text(n.bannerBodyText, -1);
}
let label = new St.Label({ style_class: 'screen-shield-notification-count-text' });
label.clutter_text.set_markup('<b>' + n.title + '</b> ' + body);
textBox.add(label);
visible = true;
}
box.visible = visible;
return [title, null];
}
_shouldShowDetails(source) {
return source.policy.detailsInLockScreen ||
source.narrowestPrivacyScope == MessageTray.PrivacyScope.SYSTEM;
}
_showSource(source, obj, box) {
if (obj.detailed) {
[obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
} else {
[obj.titleLabel, obj.countLabel] = this._makeNotificationSource(source, box);
}
box.visible = obj.visible && (source.unseenCount > 0);
}
_sourceAdded(tray, source, initial) {
let obj = {
visible: source.policy.showInLockScreen,
detailed: this._shouldShowDetails(source),
sourceDestroyId: 0,
sourceCountChangedId: 0,
sourceTitleChangedId: 0,
sourceUpdatedId: 0,
sourceBox: null,
titleLabel: null,
countLabel: null,
};
obj.sourceBox = new St.BoxLayout({ style_class: 'screen-shield-notification-source',
x_expand: true });
this._showSource(source, obj, obj.sourceBox);
this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
obj.sourceCountChangedId = source.connect('count-updated', source => {
this._countChanged(source, obj);
});
obj.sourceTitleChangedId = source.connect('title-changed', source => {
this._titleChanged(source, obj);
});
obj.policyChangedId = source.policy.connect('policy-changed', (policy, key) => {
if (key == 'show-in-lock-screen')
this._visibleChanged(source, obj);
else
this._detailedChanged(source, obj);
});
obj.sourceDestroyId = source.connect('destroy', source => {
this._onSourceDestroy(source, obj);
});
this._sources.set(source, obj);
if (!initial) {
// block scrollbars while animating, if they're not needed now
let boxHeight = this._notificationBox.height;
if (this._scrollView.height >= boxHeight)
this._scrollView.vscrollbar_policy = St.PolicyType.NEVER;
let widget = obj.sourceBox;
let [, natHeight] = widget.get_preferred_height(-1);
widget.height = 0;
widget.ease({
height: natHeight,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: 250,
onComplete: () => {
this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
widget.set_height(-1);
}
});
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
}
_titleChanged(source, obj) {
obj.titleLabel.text = source.title;
}
_countChanged(source, obj) {
// A change in the number of notifications may change whether we show
// details.
let newDetailed = this._shouldShowDetails(source);
let oldDetailed = obj.detailed;
obj.detailed = newDetailed;
if (obj.detailed || oldDetailed != newDetailed) {
// A new notification was pushed, or a previous notification was destroyed.
// Give up, and build the list again.
obj.sourceBox.destroy_all_children();
obj.titleLabel = obj.countLabel = null;
this._showSource(source, obj, obj.sourceBox);
} else {
let count = source.unseenCount;
obj.countLabel.text = this._makeNotificationCountText(count, source.isChat);
}
obj.sourceBox.visible = obj.visible && (source.unseenCount > 0);
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
_visibleChanged(source, obj) {
if (obj.visible == source.policy.showInLockScreen)
return;
obj.visible = source.policy.showInLockScreen;
obj.sourceBox.visible = obj.visible && source.unseenCount > 0;
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
_detailedChanged(source, obj) {
let newDetailed = this._shouldShowDetails(source);
if (obj.detailed == newDetailed)
return;
obj.detailed = newDetailed;
obj.sourceBox.destroy_all_children();
obj.titleLabel = obj.countLabel = null;
this._showSource(source, obj, obj.sourceBox);
}
_onSourceDestroy(source, obj) {
this._removeSource(source, obj);
this._updateVisibility();
}
_removeSource(source, obj) {
obj.sourceBox.destroy();
obj.sourceBox = obj.titleLabel = obj.countLabel = null;
source.disconnect(obj.sourceDestroyId);
source.disconnect(obj.sourceCountChangedId);
source.disconnect(obj.sourceTitleChangedId);
source.policy.disconnect(obj.policyChangedId);
this._sources.delete(source);
}
};
Signals.addSignalMethods(NotificationsBox.prototype);
var Arrow = GObject.registerClass(
class ScreenShieldArrow extends St.Bin {
_init(params) {
super._init(params);
this.x_fill = this.y_fill = true;
this._drawingArea = new St.DrawingArea();
this._drawingArea.connect('repaint', this._drawArrow.bind(this));
this.child = this._drawingArea;
this._shadowHelper = null;
this._shadowWidth = this._shadowHeight = 0;
}
_drawArrow(arrow) {
let cr = arrow.get_context();
let [w, h] = arrow.get_surface_size();
let node = this.get_theme_node();
let thickness = node.get_length('-arrow-thickness');
Clutter.cairo_set_source_color(cr, node.get_foreground_color());
cr.setLineCap(Cairo.LineCap.ROUND);
cr.setLineWidth(thickness);
cr.moveTo(thickness / 2, h - thickness / 2);
cr.lineTo(w / 2, thickness);
cr.lineTo(w - thickness / 2, h - thickness / 2);
cr.stroke();
cr.$dispose();
}
vfunc_get_paint_volume(volume) {
if (!super.vfunc_get_paint_volume(volume))
return false;
if (!this._shadow)
return true;
let shadowBox = new Clutter.ActorBox();
this._shadow.get_box(this._drawingArea.get_allocation_box(), shadowBox);
volume.set_width(Math.max(shadowBox.x2 - shadowBox.x1, volume.get_width()));
volume.set_height(Math.max(shadowBox.y2 - shadowBox.y1, volume.get_height()));
return true;
}
vfunc_style_changed() {
let node = this.get_theme_node();
this._shadow = node.get_shadow('-arrow-shadow');
if (this._shadow)
this._shadowHelper = St.ShadowHelper.new(this._shadow);
else
this._shadowHelper = null;
super.vfunc_style_changed();
}
vfunc_paint() {
if (this._shadowHelper) {
this._shadowHelper.update(this._drawingArea);
let allocation = this._drawingArea.get_allocation_box();
let paintOpacity = this._drawingArea.get_paint_opacity();
let framebuffer = Cogl.get_draw_framebuffer();
this._shadowHelper.paint(framebuffer, allocation, paintOpacity);
}
this._drawingArea.paint();
}
});
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
@ -430,67 +62,24 @@ var ScreenShield = class {
this.actor = Main.layoutManager.screenShieldGroup;
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup = new St.Widget({
x_expand: true,
y_expand: true,
reactive: true,
can_focus: true,
name: 'lockScreenGroup',
visible: false,
});
this._lockScreenGroup.connect('key-press-event',
this._onLockScreenKeyPress.bind(this));
this._lockScreenGroup.connect('scroll-event',
this._onLockScreenScroll.bind(this));
Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic');
this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(),
name: 'lockScreenContents' });
this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
this._lockScreenGroup.add_actor(this._lockScreenContents);
this._backgroundGroup = new Clutter.Actor();
this._lockScreenGroup.add_actor(this._backgroundGroup);
this._backgroundGroup.lower_bottom();
this._bgManagers = [];
this._updateBackgrounds();
Main.layoutManager.connect('monitors-changed', this._updateBackgrounds.bind(this));
this._arrowAnimationId = 0;
this._arrowWatchId = 0;
this._arrowActiveWatchId = 0;
this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
vertical: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.END,
// HACK: without these, ClutterBinLayout
// ignores alignment properties on the actor
x_expand: true,
y_expand: true });
for (let i = 0; i < N_ARROWS; i++) {
let arrow = new Arrow({ opacity: 0 });
this._arrowContainer.add_actor(arrow);
}
this._lockScreenContents.add_actor(this._arrowContainer);
this._dragAction = new Clutter.GestureAction();
this._dragAction.connect('gesture-begin', this._onDragBegin.bind(this));
this._dragAction.connect('gesture-progress', this._onDragMotion.bind(this));
this._dragAction.connect('gesture-end', this._onDragEnd.bind(this));
this._lockScreenGroup.add_action(this._dragAction);
this._lockDialogGroup = new St.Widget({ x_expand: true,
y_expand: true,
reactive: true,
pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
name: 'lockDialogGroup' });
this._lockDialogGroup.connect('key-press-event',
this._onLockScreenKeyPress.bind(this));
this.actor.add_actor(this._lockDialogGroup);
this.actor.add_actor(this._lockScreenGroup);
this._backgroundGroup = new Clutter.Actor();
this._lockDialogGroup.add_actor(this._backgroundGroup);
this._backgroundGroup.lower_bottom();
this._bgManagers = [];
this._updateBackgrounds();
Main.layoutManager.connect('monitors-changed', this._updateBackgrounds.bind(this));
this._presence = new GnomeSession.Presence((proxy, error) => {
if (error) {
@ -510,14 +99,14 @@ var ScreenShield = class {
this._smartcardManager.connect('smartcard-inserted',
(manager, token) => {
if (this._isLocked && token.UsedToLogin)
this._liftShield(true, 0);
this._liftShield();
});
this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();
this._oVirtCredentialsManager.connect('user-authenticated',
() => {
if (this._isLocked)
this._liftShield(true, 0);
this._liftShield();
});
this._loginManager = LoginManager.getLoginManager();
@ -542,7 +131,6 @@ var ScreenShield = class {
this._lockSettings.connect(`changed::${DISABLE_LOCK_KEY}`, this._syncInhibitor.bind(this));
this._isModal = false;
this._hasLockScreen = false;
this._isGreeter = false;
this._isActive = false;
this._isLocked = false;
@ -585,6 +173,7 @@ var ScreenShield = class {
_createBackground(monitorIndex) {
let monitor = Main.layoutManager.monitors[monitorIndex];
let widget = new St.Widget({ style_class: 'screen-shield-background',
clip_to_allocation: true,
x: monitor.x,
y: monitor.y,
width: monitor.width,
@ -598,12 +187,27 @@ var ScreenShield = class {
this._bgManagers.push(bgManager);
this._backgroundGroup.add_child(widget);
widget.add_effect(new Shell.BlurEffect({
blur_radius: BLUR_RADIUS,
vertical: true,
}));
widget.add_effect(new Shell.BlurEffect({
blur_radius: BLUR_RADIUS,
brightness: BLUR_BRIGHTNESS,
}));
}
_updateBackgrounds() {
let isGreeter = Main.sessionMode.isGreeter;
this._backgroundGroup.visible = !isGreeter;
for (let i = 0; i < this._bgManagers.length; i++)
this._bgManagers[i].destroy();
if (isGreeter)
return;
this._bgManagers = [];
this._backgroundGroup.destroy_all_children();
@ -611,28 +215,17 @@ var ScreenShield = class {
this._createBackground(i);
}
_liftShield(onPrimary, velocity) {
_liftShield() {
if (this._isLocked) {
if (this._ensureUnlockDialog(onPrimary, true /* allowCancel */))
this._hideLockScreen(true /* animate */, velocity);
if (this._ensureUnlockDialog(true /* allowCancel */))
this._hideLockScreen();
} else {
this.deactivate(true /* animate */);
}
}
_maybeCancelDialog() {
if (!this._dialog)
return;
this._dialog.cancel();
if (this._isGreeter) {
// LoginDialog.cancel() will grab the key focus
// on its own, so ensure it stays on lock screen
// instead
this._lockScreenGroup.grab_key_focus();
} else {
this._dialog = null;
}
}
_becomeModal() {
@ -672,31 +265,10 @@ var ScreenShield = class {
return Clutter.EVENT_PROPAGATE;
if (this._isLocked &&
this._ensureUnlockDialog(true, true) &&
this._ensureUnlockDialog(true) &&
GLib.unichar_isgraph(unichar))
this._dialog.addCharacter(unichar);
this._liftShield(true, 0);
return Clutter.EVENT_STOP;
}
_onLockScreenScroll(actor, event) {
if (this._lockScreenState != MessageTray.State.SHOWN)
return Clutter.EVENT_PROPAGATE;
let delta = 0;
if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
delta = Math.abs(event.get_scroll_delta()[0]);
else
delta = 5;
this._lockScreenScrollCounter += delta;
// 7 standard scrolls to lift up
if (this._lockScreenScrollCounter > 35) {
this._liftShield(true, 0);
}
return Clutter.EVENT_STOP;
}
@ -728,79 +300,6 @@ var ScreenShield = class {
}
}
_animateArrows() {
let arrows = this._arrowContainer.get_children();
let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1);
let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY;
for (let i = 0; i < arrows.length; i++) {
arrows[i].opacity = 0;
arrows[i].ease({
opacity: maxOpacity,
delay: unitaryDelay * (N_ARROWS - (i + 1)),
duration: ARROW_ANIMATION_TIME / 2,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
arrows[i].ease({
opacity: 0,
duration: ARROW_ANIMATION_TIME / 2,
mode: Clutter.AnimationMode.EASE_IN_QUAD
});
}
});
}
return GLib.SOURCE_CONTINUE;
}
_onDragBegin() {
this._lockScreenGroup.remove_all_transitions();
this._lockScreenState = MessageTray.State.HIDING;
if (this._isLocked)
this._ensureUnlockDialog(false, false);
return true;
}
_onDragMotion() {
let [, origY] = this._dragAction.get_press_coords(0);
let [, currentY] = this._dragAction.get_motion_coords(0);
let newY = currentY - origY;
newY = clamp(newY, -global.stage.height, 0);
this._lockScreenGroup.y = newY;
return true;
}
_onDragEnd(_action, _actor, _eventX, _eventY, _modifiers) {
if (this._lockScreenState != MessageTray.State.HIDING)
return;
if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
// Complete motion automatically
let [velocity_, velocityX_, velocityY] = this._dragAction.get_velocity(0);
this._liftShield(true, -velocityY);
} 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 duration = MANUAL_FADE_TIME * (-this._lockScreenGroup.y) / h;
this._lockScreenGroup.remove_all_transitions();
this._lockScreenGroup.ease({
y: 0,
duration,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
onComplete: () => {
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenState = MessageTray.State.SHOWN;
}
});
this._maybeCancelDialog();
}
}
_onStatusChanged(status) {
if (status != GnomeSession.PresenceStatus.IDLE)
return;
@ -904,57 +403,19 @@ var ScreenShield = class {
this.actor.show();
this._isGreeter = Main.sessionMode.isGreeter;
this._isLocked = true;
if (this._ensureUnlockDialog(true, true))
this._hideLockScreen(false, 0);
if (this._ensureUnlockDialog(true))
this._hideLockScreen();
}
_hideLockScreenComplete() {
if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen');
this._lockScreenState = MessageTray.State.HIDDEN;
this._lockScreenGroup.hide();
if (this._dialog) {
this._dialog.grab_key_focus();
this._dialog.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
}
}
_hideLockScreen(animate, velocity) {
_hideLockScreen() {
if (this._lockScreenState == MessageTray.State.HIDDEN)
return;
this._lockScreenState = MessageTray.State.HIDING;
this._lockScreenGroup.remove_all_transitions();
if (animate) {
// Tween the lock screen out of screen
// if velocity is not specified (i.e. we come here from pressing ESC),
// use the same speed regardless of original position
// if velocity is specified, it's in pixels per milliseconds
let h = global.stage.height;
let delta = (h + this._lockScreenGroup.y);
let minVelocity = global.stage.height / CURTAIN_SLIDE_TIME;
velocity = Math.max(minVelocity, velocity);
let duration = delta / velocity;
this._lockScreenGroup.ease({
y: -h,
duration,
mode: Clutter.AnimationMode.EASE_IN_QUAD,
onComplete: () => this._hideLockScreenComplete()
});
} else {
this._hideLockScreenComplete();
}
this._lockScreenState = MessageTray.State.HIDDEN;
this._cursorTracker.set_pointer_visible(true);
}
_ensureUnlockDialog(onPrimary, allowCancel) {
_ensureUnlockDialog(allowCancel) {
if (!this._dialog) {
let constructor = Main.sessionMode.unlockDialog;
if (!constructor) {
@ -967,7 +428,7 @@ var ScreenShield = class {
let time = global.get_current_time();
if (!this._dialog.open(time, onPrimary)) {
if (!this._dialog.open(time)) {
// This is kind of an impossible error: we're already modal
// by the time we reach this...
log('Could not open login dialog: failed to acquire grab');
@ -995,20 +456,17 @@ var ScreenShield = class {
if (this._lockScreenState != MessageTray.State.HIDDEN)
return;
this._ensureLockScreen();
this._lockDialogGroup.scale_x = 1;
this._lockDialogGroup.scale_y = 1;
this._ensureUnlockDialog(false);
this._lockScreenGroup.show();
this._lockScreenState = MessageTray.State.SHOWING;
let fadeToBlack = params.fadeToBlack;
if (params.animateLockScreen) {
this._lockScreenGroup.y = -global.screen_height;
this._lockScreenGroup.remove_all_transitions();
this._lockScreenGroup.ease({
y: 0,
this._lockDialogGroup.translation_y = -global.screen_height;
this._lockDialogGroup.remove_all_transitions();
this._lockDialogGroup.ease({
translation_y: 0,
duration: MANUAL_FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
@ -1016,73 +474,17 @@ var ScreenShield = class {
}
});
} else {
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenShown({ fadeToBlack: fadeToBlack,
animateFade: false });
}
this._lockScreenGroup.grab_key_focus();
this._lockDialogGroup.grab_key_focus();
if (Main.sessionMode.currentMode != 'lock-screen')
Main.sessionMode.pushMode('lock-screen');
}
_startArrowAnimation() {
this._arrowActiveWatchId = 0;
if (!this._arrowAnimationId) {
this._arrowAnimationId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 6000, this._animateArrows.bind(this));
GLib.Source.set_name_by_id(this._arrowAnimationId, '[gnome-shell] this._animateArrows');
this._animateArrows();
}
if (!this._arrowWatchId)
this._arrowWatchId = this.idleMonitor.add_idle_watch(ARROW_IDLE_TIME,
this._pauseArrowAnimation.bind(this));
}
_pauseArrowAnimation() {
if (this._arrowAnimationId) {
GLib.source_remove(this._arrowAnimationId);
this._arrowAnimationId = 0;
}
if (!this._arrowActiveWatchId)
this._arrowActiveWatchId = this.idleMonitor.add_user_active_watch(this._startArrowAnimation.bind(this));
}
_stopArrowAnimation() {
if (this._arrowAnimationId) {
GLib.source_remove(this._arrowAnimationId);
this._arrowAnimationId = 0;
}
if (this._arrowActiveWatchId) {
this.idleMonitor.remove_watch(this._arrowActiveWatchId);
this._arrowActiveWatchId = 0;
}
if (this._arrowWatchId) {
this.idleMonitor.remove_watch(this._arrowWatchId);
this._arrowWatchId = 0;
}
}
_checkArrowAnimation() {
let idleTime = this.idleMonitor.get_idletime();
if (idleTime < ARROW_IDLE_TIME)
this._startArrowAnimation();
else
this._pauseArrowAnimation();
if (Main.sessionMode.currentMode != 'unlock-dialog')
Main.sessionMode.pushMode('unlock-dialog');
}
_lockScreenShown(params) {
if (this._dialog && !this._isGreeter) {
this._dialog.destroy();
this._dialog = null;
}
this._checkArrowAnimation();
let motionId = global.stage.connect('captured-event', (stage, event) => {
if (event.type() == Clutter.EventType.MOTION) {
this._cursorTracker.set_pointer_visible(true);
@ -1094,8 +496,6 @@ var ScreenShield = class {
this._cursorTracker.set_pointer_visible(false);
this._lockScreenState = MessageTray.State.SHOWN;
this._lockScreenGroup.fixed_position_set = false;
this._lockScreenScrollCounter = 0;
if (params.fadeToBlack && params.animateFade) {
// Take a beat
@ -1118,54 +518,6 @@ var ScreenShield = class {
this.emit('lock-screen-shown');
}
// Some of the actors in the lock screen are heavy in
// resources, so we only create them when needed
_ensureLockScreen() {
if (this._hasLockScreen)
return;
this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true,
vertical: true,
style_class: 'screen-shield-contents-box' });
this._clock = new Clock();
this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
y_fill: true });
this._lockScreenContents.add_actor(this._lockScreenContentsBox);
this._notificationsBox = new NotificationsBox();
this._wakeUpScreenId = this._notificationsBox.connect('wake-up-screen', this._wakeUpScreen.bind(this));
this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
y_fill: true,
expand: true });
this._hasLockScreen = true;
}
_wakeUpScreen() {
this._onUserBecameActive();
this.emit('wake-up-screen');
}
_clearLockScreen() {
this._clock.destroy();
this._clock = null;
if (this._notificationsBox) {
this._notificationsBox.disconnect(this._wakeUpScreenId);
this._notificationsBox.destroy();
this._notificationsBox = null;
}
this._stopArrowAnimation();
this._lockScreenContentsBox.destroy();
this._hasLockScreen = false;
}
get locked() {
return this._isLocked;
@ -1180,20 +532,12 @@ var ScreenShield = class {
}
deactivate(animate) {
if (this._dialog)
this._dialog.finish(() => this._continueDeactivate(animate));
else
this._continueDeactivate(animate);
this._dialog.finish(() => this._continueDeactivate(animate));
}
_continueDeactivate(animate) {
this._hideLockScreen(animate, 0);
this._hideLockScreen();
if (this._hasLockScreen)
this._clearLockScreen();
if (Main.sessionMode.currentMode == 'lock-screen')
Main.sessionMode.popMode('lock-screen');
if (Main.sessionMode.currentMode == 'unlock-dialog')
Main.sessionMode.popMode('unlock-dialog');
@ -1210,7 +554,7 @@ var ScreenShield = class {
return;
}
if (this._dialog && !this._isGreeter)
if (!this._isGreeter)
this._dialog.popModal();
if (this._isModal) {
@ -1219,9 +563,8 @@ var ScreenShield = class {
}
this._lockDialogGroup.ease({
scale_x: 0,
scale_y: 0,
duration: animate ? Overview.ANIMATION_TIME : 0,
translation_y: -global.screen_height,
duration: CURTAIN_SLIDE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._completeDeactivate()
});
@ -1260,8 +603,7 @@ var ScreenShield = class {
this.actor.show();
if (Main.sessionMode.currentMode != 'unlock-dialog' &&
Main.sessionMode.currentMode != 'lock-screen') {
if (Main.sessionMode.currentMode != 'unlock-dialog') {
this._isGreeter = Main.sessionMode.isGreeter;
if (!this._isGreeter)
Main.sessionMode.pushMode('unlock-dialog');

View File

@ -53,19 +53,6 @@ const _modes = {
panelStyle: 'login-screen'
},
'lock-screen': {
isLocked: true,
isGreeter: undefined,
unlockDialog: undefined,
components: ['polkitAgent', 'telepathyClient'],
panel: {
left: [],
center: [],
right: ['aggregateMenu']
},
panelStyle: 'lock-screen'
},
'unlock-dialog': {
isLocked: true,
unlockDialog: undefined,

View File

@ -1,17 +1,376 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported UnlockDialog */
const { AccountsService, Atk, Clutter,
Gdm, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const { AccountsService, Atk, Clutter, Gdm, Gio, GLib,
GnomeDesktop, GObject, Meta, Shell, St } = imports.gi;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Signals = imports.signals;
const AuthPrompt = imports.gdm.authPrompt;
// The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60;
var SUMMARY_ICON_SIZE = 24;
var Clock = class {
constructor() {
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', this._updateClock.bind(this));
this._updateClock();
}
_updateClock() {
this._time.text = this._wallClock.clock;
let date = new Date();
/* Translators: This is a time format for a date in
long format */
let dateFormat = Shell.util_translate_time_string(N_("%A, %B %d"));
this._date.text = date.toLocaleFormat(dateFormat);
}
destroy() {
this.actor.destroy();
this._wallClock.run_dispose();
}
};
var NotificationsBox = class {
constructor() {
this.actor = new St.BoxLayout({ vertical: true,
name: 'screenShieldNotifications',
style_class: 'screen-shield-notifications-container' });
this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START,
hscrollbar_policy: St.PolicyType.NEVER });
this._notificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-container' });
this._scrollView.add_actor(this._notificationBox);
this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START });
this._sources = new Map();
Main.messageTray.getSources().forEach(source => {
this._sourceAdded(Main.messageTray, source, true);
});
this._updateVisibility();
this._sourceAddedId = Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
}
destroy() {
if (this._sourceAddedId) {
Main.messageTray.disconnect(this._sourceAddedId);
this._sourceAddedId = 0;
}
let items = this._sources.entries();
for (let [source, obj] of items) {
this._removeSource(source, obj);
}
this.actor.destroy();
}
_updateVisibility() {
this._notificationBox.visible =
this._notificationBox.get_children().some(a => a.visible);
this.actor.visible = this._notificationBox.visible;
}
_makeNotificationSource(source, box) {
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
box.add(sourceActor, { y_fill: true });
let title = new St.Label({
text: source.title,
style_class: 'screen-shield-notification-label',
x_expand: true,
y_align: Clutter.ActorAlign.START,
y_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
box.add_child(title);
let count = source.unseenCount;
let countLabel = new St.Label({
text: '%d'.format(count),
style_class: 'screen-shield-notification-count-text',
y_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
box.add_child(countLabel);
box.visible = count != 0;
return [title, countLabel];
}
_makeNotificationDetailedSource(source, box) {
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
let sourceBin = new St.Bin({ y_align: St.Align.START,
x_align: St.Align.START,
child: sourceActor });
box.add(sourceBin);
let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox, { y_fill: false, y_align: St.Align.START });
let title = new St.Label({ text: source.title,
style_class: 'screen-shield-notification-label' });
textBox.add(title);
let visible = false;
for (let i = 0; i < source.notifications.length; i++) {
let n = source.notifications[i];
if (n.acknowledged)
continue;
let body = '';
if (n.bannerBodyText) {
body = n.bannerBodyMarkup
? n.bannerBodyText
: GLib.markup_escape_text(n.bannerBodyText, -1);
}
let label = new St.Label({ style_class: 'screen-shield-notification-count-text' });
label.clutter_text.set_markup('<b>' + n.title + '</b> ' + body);
textBox.add(label);
visible = true;
}
box.visible = visible;
return [title, null];
}
_shouldShowDetails(source) {
return source.policy.detailsInLockScreen ||
source.narrowestPrivacyScope == MessageTray.PrivacyScope.SYSTEM;
}
_showSource(source, obj, box) {
if (obj.detailed) {
[obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
} else {
[obj.titleLabel, obj.countLabel] = this._makeNotificationSource(source, box);
}
box.visible = obj.visible && (source.unseenCount > 0);
}
_sourceAdded(tray, source, initial) {
let obj = {
visible: source.policy.showInLockScreen,
detailed: this._shouldShowDetails(source),
sourceDestroyId: 0,
sourceCountChangedId: 0,
sourceTitleChangedId: 0,
sourceUpdatedId: 0,
sourceBox: null,
titleLabel: null,
countLabel: null,
};
obj.sourceBox = new St.BoxLayout({ style_class: 'screen-shield-notification-source',
x_expand: true });
this._showSource(source, obj, obj.sourceBox);
this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
obj.sourceCountChangedId = source.connect('count-updated', source => {
this._countChanged(source, obj);
});
obj.sourceTitleChangedId = source.connect('title-changed', source => {
this._titleChanged(source, obj);
});
obj.policyChangedId = source.policy.connect('policy-changed', (policy, key) => {
if (key == 'show-in-lock-screen')
this._visibleChanged(source, obj);
else
this._detailedChanged(source, obj);
});
obj.sourceDestroyId = source.connect('destroy', source => {
this._onSourceDestroy(source, obj);
});
this._sources.set(source, obj);
if (!initial) {
// block scrollbars while animating, if they're not needed now
let boxHeight = this._notificationBox.height;
if (this._scrollView.height >= boxHeight)
this._scrollView.vscrollbar_policy = St.PolicyType.NEVER;
let widget = obj.sourceBox;
let [, natHeight] = widget.get_preferred_height(-1);
widget.height = 0;
widget.ease({
height: natHeight,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: 250,
onComplete: () => {
this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
widget.set_height(-1);
}
});
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
}
_titleChanged(source, obj) {
obj.titleLabel.text = source.title;
}
_countChanged(source, obj) {
// A change in the number of notifications may change whether we show
// details.
let newDetailed = this._shouldShowDetails(source);
let oldDetailed = obj.detailed;
obj.detailed = newDetailed;
if (obj.detailed || oldDetailed != newDetailed) {
// A new notification was pushed, or a previous notification was destroyed.
// Give up, and build the list again.
obj.sourceBox.destroy_all_children();
obj.titleLabel = obj.countLabel = null;
this._showSource(source, obj, obj.sourceBox);
} else {
let count = source.unseenCount;
obj.countLabel.text = '%d'.format(count);
}
obj.sourceBox.visible = obj.visible && (source.unseenCount > 0);
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
_visibleChanged(source, obj) {
if (obj.visible == source.policy.showInLockScreen)
return;
obj.visible = source.policy.showInLockScreen;
obj.sourceBox.visible = obj.visible && source.unseenCount > 0;
this._updateVisibility();
if (obj.sourceBox.visible)
this.emit('wake-up-screen');
}
_detailedChanged(source, obj) {
let newDetailed = this._shouldShowDetails(source);
if (obj.detailed == newDetailed)
return;
obj.detailed = newDetailed;
obj.sourceBox.destroy_all_children();
obj.titleLabel = obj.countLabel = null;
this._showSource(source, obj, obj.sourceBox);
}
_onSourceDestroy(source, obj) {
this._removeSource(source, obj);
this._updateVisibility();
}
_removeSource(source, obj) {
obj.sourceBox.destroy();
obj.sourceBox = obj.titleLabel = obj.countLabel = null;
source.disconnect(obj.sourceDestroyId);
source.disconnect(obj.sourceCountChangedId);
source.disconnect(obj.sourceTitleChangedId);
source.policy.disconnect(obj.policyChangedId);
this._sources.delete(source);
}
};
Signals.addSignalMethods(NotificationsBox.prototype);
var UnlockDialogLayout = GObject.registerClass(
class UnlockDialogLayout extends Clutter.LayoutManager {
_init(clockStack, notifications) {
super._init();
this._clockStack = clockStack;
this._notifications = notifications;
}
vfunc_get_preferred_width(container, forHeight) {
return this._clockStack.get_preferred_width(forHeight);
}
vfunc_get_preferred_height(container, forWidth) {
return this._clockStack.get_preferred_height(forWidth);
}
vfunc_allocate(container, box, flags) {
let [width, height] = box.get_size();
let tenthOfHeight = height / 10.0;
let thirdOfHeight = height / 3.0;
let [clockStackWidth, clockStackHeight] =
this._clockStack.get_preferred_size();
let [, , notificationsWidth, notificationsHeight] =
this._notifications.get_preferred_size();
let columnWidth = Math.max(clockStackWidth, notificationsWidth);
let columnX1 = Math.floor(width / 2.0 - columnWidth / 2.0);
let actorBox = new Clutter.ActorBox();
// Notifications
let maxNotificationsHeight = Math.min(
notificationsHeight,
height - tenthOfHeight - clockStackHeight);
actorBox.x1 = columnX1;
actorBox.y1 = height - maxNotificationsHeight;
actorBox.x2 = columnX1 + columnWidth;
actorBox.y2 = actorBox.y1 + maxNotificationsHeight;
this._notifications.allocate(actorBox, flags);
// Clock Stack
let clockStackY = Math.min(
thirdOfHeight,
height - clockStackHeight - maxNotificationsHeight);
actorBox.x1 = columnX1;
actorBox.y1 = clockStackY;
actorBox.x2 = columnX1 + columnWidth;
actorBox.y2 = clockStackY + clockStackHeight;
this._clockStack.allocate(actorBox, flags);
}
});
var UnlockDialog = GObject.registerClass({
Signals: { 'failed': {} },
}, class UnlockDialog extends St.Widget {
@ -19,10 +378,16 @@ var UnlockDialog = GObject.registerClass({
super._init({
accessible_role: Atk.Role.WINDOW,
style_class: 'login-dialog',
layout_manager: new Clutter.BoxLayout(),
reactive: true,
visible: false,
});
let tapAction = new Clutter.TapAction();
tapAction.connect('tap', () => {
this._showAuth();
})
this.add_action(tapAction);
this.add_constraint(new Layout.MonitorConstraint({ primary: true }));
parentActor.add_child(this);
@ -30,21 +395,31 @@ var UnlockDialog = GObject.registerClass({
this._userName = GLib.get_user_name();
this._user = this._userManager.get_user(this._userName);
this._promptBox = new St.BoxLayout({ vertical: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true });
this.add_child(this._promptBox);
let clockStack = new Shell.Stack();
this.add_child(clockStack);
this._clock = new Clock();
clockStack.add_child(this._clock.actor);
this._activePage = this._clock.actor;
this._authBox = new St.BoxLayout({
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true,
vertical: true,
visible: false,
});
clockStack.add_child(this._authBox);
this._authPrompt = new AuthPrompt.AuthPrompt(new Gdm.Client(), AuthPrompt.AuthPromptMode.UNLOCK_ONLY);
this._authPrompt.connect('failed', this._fail.bind(this));
this._authPrompt.connect('cancelled', this._fail.bind(this));
this._authPrompt.connect('reset', this._onReset.bind(this));
this._authPrompt.setPasswordChar('\u25cf');
this._authPrompt.nextButton.label = _("Unlock");
this._promptBox.add_child(this._authPrompt.actor);
this._authBox.add_child(this._authPrompt.actor);
this.allowCancel = false;
@ -59,11 +434,15 @@ var UnlockDialog = GObject.registerClass({
x_align: St.Align.START,
x_fill: false });
this._otherUserButton.connect('clicked', this._otherUserClicked.bind(this));
this._promptBox.add_child(this._otherUserButton);
this._authBox.add_child(this._otherUserButton);
} else {
this._otherUserButton = null;
}
this._notificationsBox = new NotificationsBox();
this._wakeUpScreenId = this._notificationsBox.connect('wake-up-screen', this._wakeUpScreen.bind(this));
this.add_child(this._notificationsBox.actor);
this._authPrompt.reset();
this._updateSensitivity(true);
@ -72,9 +451,81 @@ var UnlockDialog = GObject.registerClass({
this._idleMonitor = Meta.IdleMonitor.get_core();
this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, this._escape.bind(this));
this.layout_manager = new UnlockDialogLayout(
clockStack,
this._notificationsBox.actor);
this.connect('destroy', this._onDestroy.bind(this));
}
_showClock() {
if (this._activePage == this._clock.actor)
return;
this._activePage = this._clock.actor;
this._clock.actor.show();
this._authBox.ease({
opacity: 0,
duration: 300,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._authBox.hide(),
});
this._clock.actor.ease({
opacity: 255,
duration: 300,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
_showAuth() {
if (this._activePage == this._authBox)
return;
this._activePage = this._authBox;
this._authBox.show();
this._clock.actor.ease({
opacity: 0,
duration: 300,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._clock.actor.hide(),
});
this._authBox.ease({
opacity: 255,
duration: 300,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
vfunc_captured_event(event) {
if (event.type() != Clutter.EventType.KEY_PRESS)
return Clutter.EVENT_PROPAGATE;
if (this._activePage == this._authBox)
return Clutter.EVENT_PROPAGATE;
let symbol = event.get_key_symbol();
let unichar = event.get_key_unicode();
let isEnter = (symbol == Clutter.KEY_Return ||
symbol == Clutter.KEY_KP_Enter ||
symbol == Clutter.KEY_ISO_Enter);
let isEscape = (symbol == Clutter.KEY_Escape);
let isLiftChar = (GLib.unichar_isprint(unichar) &&
(this._activePage == this._clock.actor ||
!GLib.unichar_isgraph(unichar)));
if (!isEnter && !isEscape && !isLiftChar)
return Clutter.EVENT_PROPAGATE;
this._showAuth();
return Clutter.EVENT_PROPAGATE;
}
_updateSensitivity(sensitive) {
this._authPrompt.updateSensitivity(sensitive);
@ -85,6 +536,7 @@ var UnlockDialog = GObject.registerClass({
}
_fail() {
this._showClock();
this.emit('failed');
}
@ -111,9 +563,24 @@ var UnlockDialog = GObject.registerClass({
this._authPrompt.cancel();
}
_wakeUpScreen() {
// FIXME
//this._onUserBecameActive();
this.emit('wake-up-screen');
}
_onDestroy() {
this.popModal();
if (this._notificationsBox) {
this._notificationsBox.disconnect(this._wakeUpScreenId);
this._notificationsBox.destroy();
this._notificationsBox = null;
}
this._clock.destroy();
this._clock = null;
if (this._idleWatchId) {
this._idleMonitor.remove_watch(this._idleWatchId);
this._idleWatchId = 0;
@ -122,11 +589,10 @@ var UnlockDialog = GObject.registerClass({
cancel() {
this._authPrompt.cancel();
this.destroy();
}
addCharacter(unichar) {
this._showAuth();
this._authPrompt.addCharacter(unichar);
}

View File

@ -95,6 +95,7 @@ libshell_public_headers = [
'shell-app.h',
'shell-app-system.h',
'shell-app-usage.h',
'shell-blur-effect.h',
'shell-embedded-window.h',
'shell-glsl-effect.h',
'shell-gtk-embed.h',
@ -129,6 +130,7 @@ libshell_sources = [
'shell-app.c',
'shell-app-system.c',
'shell-app-usage.c',
'shell-blur-effect.c',
'shell-embedded-window.c',
'shell-embedded-window-private.h',
'shell-global.c',

506
src/shell-blur-effect.c Normal file
View File

@ -0,0 +1,506 @@
/* shell-blur-effect.c
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "shell-blur-effect.h"
static const gchar *gaussian_blur_glsl_declarations =
"uniform float blur_radius; \n"
"uniform float brightness; \n"
"uniform float pixel_step; \n"
"uniform int vertical; \n"
" \n"
"float gaussian (float sigma, float x) { \n"
" return exp ( - (x * x) / (2.0 * sigma * sigma)); \n"
"} \n"
" \n";
static const gchar *gaussian_blur_glsl =
" float radius = blur_radius * 2.30348; \n"
" float total = 0.0; \n"
" vec4 ret = vec4 (0); \n"
" vec2 uv = vec2(cogl_tex_coord.st); \n"
" \n"
" int half_radius = max(int(radius / 2.0), 1); \n"
" \n"
" if (vertical != 0) { \n"
" for (int y = -half_radius; y < half_radius; y++) { \n"
" float fy = gaussian (radius / 2.0, float(y)); \n"
" float offset_y = float(y) * pixel_step; \n"
" \n"
" vec4 c = texture2D(cogl_sampler, uv + vec2(0.0, offset_y)); \n"
" total += fy; \n"
" ret += c * fy; \n"
" } \n"
" } else { \n"
" for (int x = -half_radius; x < half_radius; x++) { \n"
" float fx = gaussian (radius / 2.0, float(x)); \n"
" float offset_x = float(x) * pixel_step; \n"
" \n"
" vec4 c = texture2D(cogl_sampler, uv + vec2(offset_x, 0.0)); \n"
" total += fx; \n"
" ret += c * fx; \n"
" } \n"
" } \n"
" \n"
" cogl_texel = vec4 (ret / total); \n"
" cogl_texel.rgb *= brightness; \n";
#define DOWNSCALE_FACTOR 4.0
struct _ShellBlurEffect
{
ClutterOffscreenEffect parent_instance;
CoglPipeline *pipeline;
int blur_radius_uniform;
int brightness_uniform;
int pixel_step_uniform;
int vertical_uniform;
unsigned int tex_width;
unsigned int tex_height;
gboolean vertical;
float brightness;
int blur_radius;
};
G_DEFINE_TYPE (ShellBlurEffect, shell_blur_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT)
enum {
PROP_0,
PROP_BLUR_RADIUS,
PROP_BRIGHTNESS,
PROP_VERTICAL,
N_PROPS
};
static GParamSpec *properties [N_PROPS] = { NULL, };
static CoglPipeline*
create_pipeline (void)
{
static CoglPipeline *base_pipeline = NULL;
if (G_UNLIKELY (base_pipeline == NULL))
{
CoglSnippet *snippet;
CoglContext *ctx =
clutter_backend_get_cogl_context (clutter_get_default_backend ());
base_pipeline = cogl_pipeline_new (ctx);
snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_TEXTURE_LOOKUP,
gaussian_blur_glsl_declarations,
NULL);
cogl_snippet_set_replace (snippet, gaussian_blur_glsl);
cogl_pipeline_add_layer_snippet (base_pipeline, 0, snippet);
cogl_object_unref (snippet);
cogl_pipeline_set_layer_null_texture (base_pipeline, 0);
cogl_pipeline_set_layer_filters (base_pipeline,
0,
COGL_PIPELINE_FILTER_LINEAR,
COGL_PIPELINE_FILTER_LINEAR);
cogl_pipeline_set_layer_wrap_mode (base_pipeline,
0,
COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
}
return cogl_pipeline_copy (base_pipeline);
}
static void
queue_actor_repaint (ShellBlurEffect *self)
{
ClutterActor *actor =
clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
if (actor)
clutter_actor_queue_redraw (actor);
}
static void
update_uniforms (ShellBlurEffect *self)
{
ClutterOffscreenEffect *offscreen_effect =
CLUTTER_OFFSCREEN_EFFECT (self);
CoglHandle texture;
texture = clutter_offscreen_effect_get_texture (offscreen_effect);
self->tex_width = cogl_texture_get_width (texture) * DOWNSCALE_FACTOR;
self->tex_height = cogl_texture_get_height (texture) * DOWNSCALE_FACTOR;
if (self->pixel_step_uniform > -1)
{
ClutterRect rect;
float pixel_step;
clutter_offscreen_effect_get_target_rect (CLUTTER_OFFSCREEN_EFFECT (self),
&rect);
if (self->vertical)
pixel_step = 1.f / rect.size.height;
else
pixel_step = 1.f / rect.size.width;
cogl_pipeline_set_uniform_1f (self->pipeline,
self->pixel_step_uniform,
pixel_step / DOWNSCALE_FACTOR);
}
if (self->blur_radius_uniform > -1)
{
cogl_pipeline_set_uniform_1f (self->pipeline,
self->blur_radius_uniform,
self->blur_radius / DOWNSCALE_FACTOR);
}
if (self->brightness_uniform > -1)
{
cogl_pipeline_set_uniform_1f (self->pipeline,
self->brightness_uniform,
self->brightness);
}
if (self->vertical_uniform > -1)
{
cogl_pipeline_set_uniform_1i (self->pipeline,
self->vertical_uniform,
self->vertical);
}
cogl_pipeline_set_layer_texture (self->pipeline, 0, texture);
}
static gboolean
shell_blur_effect_pre_paint (ClutterEffect *effect)
{
gboolean success;
success = CLUTTER_EFFECT_CLASS (shell_blur_effect_parent_class)->pre_paint (effect);
if (success)
{
CoglFramebuffer *offscreen = cogl_get_draw_framebuffer ();
/* Texture is downscaled, draw downscaled as well */
cogl_framebuffer_scale (offscreen,
1.0 / DOWNSCALE_FACTOR,
1.0 / DOWNSCALE_FACTOR,
1.0);
}
return success;
}
static void
shell_blur_effect_paint_target (ClutterOffscreenEffect *effect)
{
ShellBlurEffect *self = SHELL_BLUR_EFFECT (effect);
CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer ();
ClutterActor *actor;
float blur_radius_offset_h;
float blur_radius_offset_v;
guint8 paint_opacity;
update_uniforms (self);
actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
paint_opacity = clutter_actor_get_paint_opacity (actor);
cogl_pipeline_set_color4ub (self->pipeline,
paint_opacity,
paint_opacity,
paint_opacity,
paint_opacity);
blur_radius_offset_h =
self->blur_radius / (float) self->tex_width / DOWNSCALE_FACTOR;
blur_radius_offset_v =
self->blur_radius / (float) self->tex_height / DOWNSCALE_FACTOR;
cogl_framebuffer_draw_textured_rectangle (framebuffer,
self->pipeline,
0, 0,
self->tex_width,
self->tex_height,
blur_radius_offset_h,
blur_radius_offset_v,
1.f - blur_radius_offset_h,
1.f - blur_radius_offset_v);
}
static void
shell_blur_effect_post_paint (ClutterEffect *effect)
{
CoglFramebuffer *offscreen = cogl_get_draw_framebuffer ();
cogl_framebuffer_scale (offscreen,
DOWNSCALE_FACTOR,
DOWNSCALE_FACTOR,
1.f);
CLUTTER_EFFECT_CLASS (shell_blur_effect_parent_class)->post_paint (effect);
}
static CoglTexture*
shell_blur_effect_create_texture (ClutterOffscreenEffect *effect,
float width,
float height)
{
CoglContext *ctx =
clutter_backend_get_cogl_context (clutter_get_default_backend ());
return cogl_texture_2d_new_with_size (ctx,
width / DOWNSCALE_FACTOR,
height / DOWNSCALE_FACTOR);
}
static gboolean
shell_blur_effect_modify_paint_volume (ClutterEffect *effect,
ClutterPaintVolume *volume)
{
ShellBlurEffect *self = SHELL_BLUR_EFFECT (effect);
ClutterVertex origin;
float width;
float height;
clutter_paint_volume_get_origin (volume, &origin);
width = clutter_paint_volume_get_width (volume);
height = clutter_paint_volume_get_height (volume);
if (self->vertical)
{
origin.y -= self->blur_radius;
height += 2 * self->blur_radius;
}
else
{
origin.x -= self->blur_radius;
width += 2 * self->blur_radius;
}
clutter_paint_volume_set_origin (volume, &origin);
clutter_paint_volume_set_width (volume, width);
clutter_paint_volume_set_height (volume, height);
return TRUE;
}
static void
shell_blur_effect_finalize (GObject *object)
{
ShellBlurEffect *self = (ShellBlurEffect *)object;
g_clear_pointer (&self->pipeline, cogl_object_unref);
G_OBJECT_CLASS (shell_blur_effect_parent_class)->finalize (object);
}
static void
shell_blur_effect_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
switch (prop_id)
{
case PROP_BLUR_RADIUS:
g_value_set_int (value, self->blur_radius);
break;
case PROP_BRIGHTNESS:
g_value_set_int (value, self->brightness);
break;
case PROP_VERTICAL:
g_value_set_boolean (value, self->vertical);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
shell_blur_effect_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ShellBlurEffect *self = SHELL_BLUR_EFFECT (object);
switch (prop_id)
{
case PROP_BLUR_RADIUS:
shell_blur_effect_set_blur_radius (self, g_value_get_int (value));
break;
case PROP_BRIGHTNESS:
shell_blur_effect_set_brightness (self, g_value_get_float (value));
break;
case PROP_VERTICAL:
shell_blur_effect_set_vertical (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
shell_blur_effect_class_init (ShellBlurEffectClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
ClutterOffscreenEffectClass *offscreen_class =
CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
object_class->finalize = shell_blur_effect_finalize;
object_class->get_property = shell_blur_effect_get_property;
object_class->set_property = shell_blur_effect_set_property;
effect_class->pre_paint = shell_blur_effect_pre_paint;
effect_class->post_paint = shell_blur_effect_post_paint;
effect_class->modify_paint_volume = shell_blur_effect_modify_paint_volume;
offscreen_class->paint_target = shell_blur_effect_paint_target;
offscreen_class->create_texture = shell_blur_effect_create_texture;
properties[PROP_BLUR_RADIUS] =
g_param_spec_int ("blur-radius",
"Blur radius",
"Blur radius",
0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
properties[PROP_BRIGHTNESS] =
g_param_spec_float ("brightness",
"Brightness",
"Brightness",
0.f, 1.f, 1.f,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
properties[PROP_VERTICAL] =
g_param_spec_boolean ("vertical",
"Vertical",
"Whether the blur is vertical or horizontal",
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
shell_blur_effect_init (ShellBlurEffect *self)
{
self->blur_radius = 0;
self->brightness = 1.f;
self->vertical = FALSE;
self->pipeline = create_pipeline ();
self->blur_radius_uniform =
cogl_pipeline_get_uniform_location (self->pipeline, "blur_radius");
self->brightness_uniform =
cogl_pipeline_get_uniform_location (self->pipeline, "brightness");
self->pixel_step_uniform =
cogl_pipeline_get_uniform_location (self->pipeline, "pixel_step");
self->vertical_uniform =
cogl_pipeline_get_uniform_location (self->pipeline, "vertical");
}
ShellBlurEffect *
shell_blur_effect_new (void)
{
return g_object_new (SHELL_TYPE_BLUR_EFFECT, NULL);
}
int
shell_blur_effect_get_blur_radius (ShellBlurEffect *self)
{
g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), -1);
return self->blur_radius;
}
void
shell_blur_effect_set_blur_radius (ShellBlurEffect *self,
int radius)
{
g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
if (self->blur_radius == radius)
return;
self->blur_radius = radius;
queue_actor_repaint (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BLUR_RADIUS]);
}
float
shell_blur_effect_get_brightness (ShellBlurEffect *self)
{
g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), FALSE);
return self->brightness;
}
void
shell_blur_effect_set_brightness (ShellBlurEffect *self,
float brightness)
{
g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
if (self->brightness == brightness)
return;
self->brightness = brightness;
queue_actor_repaint (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BRIGHTNESS]);
}
gboolean
shell_blur_effect_get_vertical (ShellBlurEffect *self)
{
g_return_val_if_fail (SHELL_IS_BLUR_EFFECT (self), FALSE);
return self->vertical;
}
void
shell_blur_effect_set_vertical (ShellBlurEffect *self,
gboolean vertical)
{
g_return_if_fail (SHELL_IS_BLUR_EFFECT (self));
if (self->vertical == vertical)
return;
self->vertical = vertical;
queue_actor_repaint (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VERTICAL]);
}

45
src/shell-blur-effect.h Normal file
View File

@ -0,0 +1,45 @@
/* shell-blur-effect.h
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <clutter/clutter.h>
G_BEGIN_DECLS
#define SHELL_TYPE_BLUR_EFFECT (shell_blur_effect_get_type())
G_DECLARE_FINAL_TYPE (ShellBlurEffect, shell_blur_effect, SHELL, BLUR_EFFECT, ClutterOffscreenEffect)
ShellBlurEffect *shell_blur_effect_new (void);
int shell_blur_effect_get_blur_radius (ShellBlurEffect *self);
void shell_blur_effect_set_blur_radius (ShellBlurEffect *self,
int radius);
float shell_blur_effect_get_brightness (ShellBlurEffect *self);
void shell_blur_effect_set_brightness (ShellBlurEffect *self,
float brightness);
gboolean shell_blur_effect_get_vertical (ShellBlurEffect *self);
void shell_blur_effect_set_vertical (ShellBlurEffect *self,
gboolean vertical);
G_END_DECLS