Skip to content

Commit 347c39c

Browse files
committed
userWidget.js: Fix updating the image when the user avatar changes.
Having the user's avatar picture set as a background-image is nice because it allows us to round the image or add a border easily via themes, but when the user chooses a new image, it's not so easy to force the image to be updated, even though the StTextureCache notices the change and drops it from its own cache. This is mainly an issue when selecting a custom image from a user directory or a photo - AccountsService copies these to /var/lib/ AccountsService/icons/<user>. So, the file may change but the path does not, which keeps Cinnamon from updating it. Monitor the texture cache and recreate the avatar image entirely when it changes instead - this forces an entirely new actor and theme context for it, and gets the proper updated image.
1 parent fed9a1c commit 347c39c

1 file changed

Lines changed: 80 additions & 40 deletions

File tree

js/ui/userWidget.js

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,86 +14,101 @@ const Params = imports.misc.params;
1414
var AVATAR_ICON_SIZE = 64;
1515

1616
var Avatar = GObject.registerClass(
17-
class Avatar extends St.Bin {
17+
class Avatar extends Clutter.Actor {
1818
_init(user, params) {
1919
let themeContext = St.ThemeContext.get_for_stage(global.stage);
2020
params = Params.parse(params, {
2121
styleClass: 'user-icon',
2222
reactive: true,
23-
track_hover: true,
2423
iconSize: AVATAR_ICON_SIZE,
2524
});
2625

2726
super._init({
28-
style_class: params.styleClass,
27+
layout_manager: new Clutter.BinLayout(),
2928
reactive: params.reactive,
3029
width: params.iconSize * themeContext.scaleFactor,
3130
height: params.iconSize * themeContext.scaleFactor,
3231
});
3332

34-
this.connect('notify::hover', this._onHoverChanged.bind(this));
35-
36-
this.set_important(true);
33+
this._styleClass = params.styleClass;
3734
this._iconSize = params.iconSize;
3835
this._user = user;
36+
this._iconFile = null;
37+
this._child = null;
3938

40-
this.bind_property('reactive', this, 'track-hover',
41-
GObject.BindingFlags.SYNC_CREATE);
4239
this.bind_property('reactive', this, 'can-focus',
4340
GObject.BindingFlags.SYNC_CREATE);
4441

4542
// Monitor the scaling factor to make sure we recreate the avatar when needed.
4643
this._scaleFactorChangeId =
4744
themeContext.connect('notify::scale-factor', this.update.bind(this));
4845

46+
// Monitor for changes to the icon file on disk
47+
this._textureCache = St.TextureCache.get_default();
48+
this._textureFileChangedId =
49+
this._textureCache.connect('texture-file-changed', this._onTextureFileChanged.bind(this));
50+
4951
this.connect('destroy', this._onDestroy.bind(this));
5052
}
5153

5254
_onHoverChanged() {
53-
if (this.hover) {
54-
if (this.child) {
55-
this.child.add_style_class_name('highlighted');
56-
}
57-
else {
55+
if (!this._child)
56+
return;
57+
58+
if (this._child.hover) {
59+
if (this._iconFile) {
5860
let effect = new Clutter.BrightnessContrastEffect();
5961
effect.set_brightness(0.2);
6062
effect.set_contrast(0.3);
61-
this.add_effect(effect);
63+
this._child.add_effect(effect);
64+
} else {
65+
this._child.add_style_class_name('highlighted');
6266
}
63-
this.add_accessible_state(Atk.StateType.FOCUSED);
67+
this._child.add_accessible_state(Atk.StateType.FOCUSED);
6468
} else {
65-
if (this.child) {
66-
this.child.remove_style_class_name('highlighted');
69+
if (this._iconFile) {
70+
this._child.clear_effects();
71+
} else {
72+
this._child.remove_style_class_name('highlighted');
6773
}
68-
else {
69-
this.clear_effects();
70-
}
71-
this.remove_accessible_state(Atk.StateType.FOCUSED);
74+
this._child.remove_accessible_state(Atk.StateType.FOCUSED);
7275
}
7376
}
7477

75-
vfunc_style_changed() {
76-
super.vfunc_style_changed();
77-
78-
let node = this.get_theme_node();
78+
_onStyleChanged() {
79+
let node = this._child.get_theme_node();
7980
let [found, iconSize] = node.lookup_length('icon-size', false);
8081

8182
if (!found)
8283
return;
8384

8485
let themeContext = St.ThemeContext.get_for_stage(global.stage);
8586

86-
// node.lookup_length() returns a scaled value, but we
87-
// need unscaled
88-
this._iconSize = iconSize / themeContext.scaleFactor;
89-
this.update();
87+
// node.lookup_length() returns a scaled value, but we need unscaled
88+
let newIconSize = iconSize / themeContext.scaleFactor;
89+
90+
if (newIconSize !== this._iconSize) {
91+
this._iconSize = newIconSize;
92+
this.update();
93+
}
9094
}
9195

9296
_onDestroy() {
9397
if (this._scaleFactorChangeId) {
9498
let themeContext = St.ThemeContext.get_for_stage(global.stage);
9599
themeContext.disconnect(this._scaleFactorChangeId);
96-
delete this._scaleFactorChangeId;
100+
this._scaleFactorChangeId = 0;
101+
}
102+
103+
if (this._textureFileChangedId) {
104+
this._textureCache.disconnect(this._textureFileChangedId);
105+
this._textureFileChangedId = 0;
106+
}
107+
}
108+
109+
_onTextureFileChanged(cache, file) {
110+
if (this._iconFile && file.get_path() === this._iconFile) {
111+
this.update();
97112
}
98113
}
99114

@@ -114,24 +129,49 @@ class Avatar extends St.Bin {
114129
iconFile = null;
115130
}
116131

132+
this._iconFile = iconFile;
133+
117134
let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
118135
this.set_size(
119136
this._iconSize * scaleFactor,
120137
this._iconSize * scaleFactor);
121138

139+
// Remove old child
140+
if (this._child) {
141+
this._child.destroy();
142+
this._child = null;
143+
}
144+
145+
let size = this._iconSize * scaleFactor;
146+
122147
if (iconFile) {
123-
this.child = null;
124-
this.add_style_class_name('user-avatar');
125-
this.style = `
126-
background-image: url("${iconFile}");
127-
background-size: cover;`;
148+
this._child = new St.Bin({
149+
style_class: `${this._styleClass} user-avatar`,
150+
reactive: this.reactive,
151+
track_hover: this.reactive,
152+
width: size,
153+
height: size,
154+
style: `background-image: url("${iconFile}"); background-size: cover;`,
155+
});
128156
} else {
129-
this.style = null;
130-
this.child = new St.Icon({
131-
icon_name: 'xsi-avatar-default-symbolic',
132-
icon_size: this._iconSize,
157+
this._child = new St.Bin({
158+
style_class: this._styleClass,
159+
reactive: this.reactive,
160+
track_hover: this.reactive,
161+
width: size,
162+
height: size,
163+
child: new St.Icon({
164+
icon_name: 'xsi-avatar-default-symbolic',
165+
icon_size: this._iconSize,
166+
}),
133167
});
134168
}
169+
170+
this._child.set_important(true);
171+
this._child.connect('notify::hover', this._onHoverChanged.bind(this));
172+
this._child.connect('style-changed', this._onStyleChanged.bind(this));
173+
174+
this.add_child(this._child);
135175
}
136176
});
137177

@@ -155,7 +195,7 @@ class UserWidget extends St.BoxLayout {
155195

156196
this._avatar = new Avatar(user);
157197
this._avatar.x_align = Clutter.ActorAlign.CENTER;
158-
this.add(this._avatar, { x_fill: false });
198+
this.add_child(this._avatar);
159199

160200
this._label = new St.Label({ style_class: 'user-widget-label' });
161201
this._label.y_align = Clutter.ActorAlign.CENTER;

0 commit comments

Comments
 (0)