8566ec2ee5
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
295 lines
8.8 KiB
JavaScript
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();
|
|
}
|
|
});
|