gnome-shell/js/ui/osdWindow.js
Florian Müllner 8566ec2ee5 osdWindow: Disconnect signals on destroy
Since we started to show OSD windows on all monitors, OSD windows are
destroyed when the corresponding monitor is disconnected. We shouldn't
leave any signal handlers around in that case - they prevent the object
from being garbage collected, and trigger warnings for accessing proper-
ties of invalidated GObjects.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/602
2018-10-22 14:58:50 +00:00

295 lines
8.8 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const St = imports.gi.St;
const BarLevel = imports.ui.barLevel;
const Lang = imports.lang;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Mainloop = imports.mainloop;
const Tweener = imports.ui.tweener;
const Meta = imports.gi.Meta;
var HIDE_TIMEOUT = 1500;
var FADE_TIME = 0.1;
var LEVEL_ANIMATION_TIME = 0.1;
var LevelBar = new Lang.Class({
Name: 'LevelBar',
Extends: BarLevel.BarLevel,
_init() {
this._level = 0;
this._maxLevel = 100;
let params = {
styleClass: 'level',
}
this.parent(this._level, params);
this.actor.accessible_name = _("Volume");
this.actor.connect('notify::width', () => { this.level = this.level; });
},
get level() {
return this._level;
},
set level(value) {
this._level = Math.max(0, Math.min(value, this._maxLevel));
this.setValue(this._level / 100);
},
get maxLevel() {
return this._maxLevel;
},
set maxLevel(value) {
this._maxLevel = Math.max(100, value);
this.setMaximumValue(this._maxLevel / 100);
}
});
var OsdWindowConstraint = new Lang.Class({
Name: 'OsdWindowConstraint',
Extends: Clutter.Constraint,
_init(props) {
this._minSize = 0;
this.parent(props);
},
set minSize(v) {
this._minSize = v;
if (this.actor)
this.actor.queue_relayout();
},
vfunc_update_allocation(actor, actorBox) {
// Clutter will adjust the allocation for margins,
// so add it to our minimum size
let minSize = this._minSize + actor.margin_top + actor.margin_bottom;
let [width, height] = actorBox.get_size();
// Enforce a ratio of 1
let size = Math.ceil(Math.max(minSize, height));
actorBox.set_size(size, size);
// Recenter
let [x, y] = actorBox.get_origin();
actorBox.set_origin(Math.ceil(x + width / 2 - size / 2),
Math.ceil(y + height / 2 - size / 2));
}
});
var OsdWindow = new Lang.Class({
Name: 'OsdWindow',
_init(monitorIndex) {
this.actor = new St.Widget({ x_expand: true,
y_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER });
this._monitorIndex = monitorIndex;
let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
this.actor.add_constraint(constraint);
this._boxConstraint = new OsdWindowConstraint();
this._box = new St.BoxLayout({ style_class: 'osd-window',
vertical: true });
this._box.add_constraint(this._boxConstraint);
this.actor.add_actor(this._box);
this._icon = new St.Icon();
this._box.add(this._icon, { expand: true });
this._label = new St.Label();
this._box.add(this._label);
this._level = new LevelBar();
this._box.add(this._level.actor);
this._hideTimeoutId = 0;
this._reset();
this.actor.connect('destroy', this._onDestroy.bind(this));
this._monitorsChangedId =
Main.layoutManager.connect('monitors-changed',
this._relayout.bind(this));
let themeContext = St.ThemeContext.get_for_stage(global.stage);
this._scaleChangedId =
themeContext.connect('notify::scale-factor',
this._relayout.bind(this));
this._relayout();
Main.uiGroup.add_child(this.actor);
},
_onDestroy() {
if (this._monitorsChangedId)
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
let themeContext = St.ThemeContext.get_for_stage(global.stage);
if (this._scaleChangedId)
themeContext.disconnect(this._scaleChangedId);
this._scaleChangedId = 0;
},
setIcon(icon) {
this._icon.gicon = icon;
},
setLabel(label) {
this._label.visible = (label != undefined);
if (label)
this._label.text = label;
},
setLevel(level) {
this._level.actor.visible = (level != undefined);
if (level != undefined) {
if (this.actor.visible)
Tweener.addTween(this._level,
{ level: level,
time: LEVEL_ANIMATION_TIME,
transition: 'easeOutQuad' });
else
this._level.level = level;
}
},
setMaxLevel(maxLevel) {
if (maxLevel === undefined)
maxLevel = 100;
this._level.maxLevel = maxLevel;
},
show() {
if (!this._icon.gicon)
return;
if (!this.actor.visible) {
Meta.disable_unredirect_for_display(global.display);
this.actor.show();
this.actor.opacity = 0;
this.actor.get_parent().set_child_above_sibling(this.actor, null);
Tweener.addTween(this.actor,
{ opacity: 255,
time: FADE_TIME,
transition: 'easeOutQuad' });
}
if (this._hideTimeoutId)
Mainloop.source_remove(this._hideTimeoutId);
this._hideTimeoutId = Mainloop.timeout_add(HIDE_TIMEOUT,
this._hide.bind(this));
GLib.Source.set_name_by_id(this._hideTimeoutId, '[gnome-shell] this._hide');
},
cancel() {
if (!this._hideTimeoutId)
return;
Mainloop.source_remove(this._hideTimeoutId);
this._hide();
},
_hide() {
this._hideTimeoutId = 0;
Tweener.addTween(this.actor,
{ opacity: 0,
time: FADE_TIME,
transition: 'easeOutQuad',
onComplete: () => {
this._reset();
Meta.enable_unredirect_for_display(global.display);
}
});
return GLib.SOURCE_REMOVE;
},
_reset() {
this.actor.hide();
this.setLabel(null);
this.setMaxLevel(null);
this.setLevel(null);
},
_relayout() {
/* assume 110x110 on a 640x480 display and scale from there */
let monitor = Main.layoutManager.monitors[this._monitorIndex];
if (!monitor)
return; // we are about to be removed
let scalew = monitor.width / 640.0;
let scaleh = monitor.height / 480.0;
let scale = Math.min(scalew, scaleh);
let popupSize = 110 * Math.max(1, scale);
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
this._icon.icon_size = popupSize / (2 * scaleFactor);
this._box.translation_y = Math.round(monitor.height / 4);
this._boxConstraint.minSize = popupSize;
}
});
var OsdWindowManager = new Lang.Class({
Name: 'OsdWindowManager',
_init() {
this._osdWindows = [];
Main.layoutManager.connect('monitors-changed',
this._monitorsChanged.bind(this));
this._monitorsChanged();
},
_monitorsChanged() {
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
if (this._osdWindows[i] == undefined)
this._osdWindows[i] = new OsdWindow(i);
}
for (let i = Main.layoutManager.monitors.length; i < this._osdWindows.length; i++) {
this._osdWindows[i].actor.destroy();
this._osdWindows[i] = null;
}
this._osdWindows.length = Main.layoutManager.monitors.length;
},
_showOsdWindow(monitorIndex, icon, label, level, maxLevel) {
this._osdWindows[monitorIndex].setIcon(icon);
this._osdWindows[monitorIndex].setLabel(label);
this._osdWindows[monitorIndex].setMaxLevel(maxLevel);
this._osdWindows[monitorIndex].setLevel(level);
this._osdWindows[monitorIndex].show();
},
show(monitorIndex, icon, label, level, maxLevel) {
if (monitorIndex != -1) {
for (let i = 0; i < this._osdWindows.length; i++) {
if (i == monitorIndex)
this._showOsdWindow(i, icon, label, level, maxLevel);
else
this._osdWindows[i].cancel();
}
} else {
for (let i = 0; i < this._osdWindows.length; i++)
this._showOsdWindow(i, icon, label, level, maxLevel);
}
},
hideAll() {
for (let i = 0; i < this._osdWindows.length; i++)
this._osdWindows[i].cancel();
}
});