volume: Clean up stream/slider handling code
Rather than using naming schemes and dynamic property lookups as a kind of namespace, use what was designed to be used as a namespace: a class. https://bugzilla.gnome.org/show_bug.cgi?id=690539
This commit is contained in:
parent
1a4948f0f2
commit
c9d0e82c52
@ -5,6 +5,7 @@ const Lang = imports.lang;
|
|||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Gvc = imports.gi.Gvc;
|
const Gvc = imports.gi.Gvc;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
const PopupMenu = imports.ui.popupMenu;
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
@ -26,61 +27,134 @@ function getMixerControl() {
|
|||||||
return _mixerControl;
|
return _mixerControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VolumeMenu = new Lang.Class({
|
const StreamSlider = new Lang.Class({
|
||||||
Name: 'VolumeMenu',
|
Name: 'StreamSlider',
|
||||||
Extends: PopupMenu.PopupMenuSection,
|
|
||||||
|
|
||||||
_init: function(control) {
|
|
||||||
this.parent();
|
|
||||||
|
|
||||||
this._hasHeadphones = false;
|
|
||||||
|
|
||||||
|
_init: function(control, title) {
|
||||||
this._control = control;
|
this._control = control;
|
||||||
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
|
|
||||||
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
|
|
||||||
this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
|
|
||||||
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
|
|
||||||
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
|
|
||||||
this._volumeMax = this._control.get_vol_max_norm();
|
|
||||||
|
|
||||||
this._output = null;
|
this.item = new PopupMenu.PopupMenuSection();
|
||||||
this._outputVolumeId = 0;
|
|
||||||
this._outputMutedId = 0;
|
|
||||||
/* Translators: This is the label for audio volume */
|
|
||||||
this._outputTitle = new PopupMenu.PopupMenuItem(_("Volume"), { reactive: false });
|
|
||||||
this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
|
||||||
this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
|
|
||||||
this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
|
||||||
this.addMenuItem(this._outputTitle);
|
|
||||||
this.addMenuItem(this._outputSlider);
|
|
||||||
|
|
||||||
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
this._title = new PopupMenu.PopupMenuItem(title, { reactive: false });
|
||||||
|
this._slider = new PopupMenu.PopupSliderMenuItem(0);
|
||||||
|
this._slider.connect('value-changed', Lang.bind(this, this._sliderChanged));
|
||||||
|
this._slider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
||||||
|
|
||||||
this._input = null;
|
this.item.addMenuItem(this._title);
|
||||||
this._inputVolumeId = 0;
|
this.item.addMenuItem(this._slider);
|
||||||
this._inputMutedId = 0;
|
|
||||||
this._inputTitle = new PopupMenu.PopupMenuItem(_("Microphone"), { reactive: false });
|
|
||||||
this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
|
||||||
this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
|
|
||||||
this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
|
||||||
this.addMenuItem(this._inputTitle);
|
|
||||||
this.addMenuItem(this._inputSlider);
|
|
||||||
|
|
||||||
this._onControlStateChanged();
|
this._stream = null;
|
||||||
|
this._shouldShow = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get stream() {
|
||||||
|
return this._stream;
|
||||||
|
},
|
||||||
|
|
||||||
|
set stream(stream) {
|
||||||
|
if (this._stream) {
|
||||||
|
this._disconnectStream(this._stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stream = stream;
|
||||||
|
|
||||||
|
if (this._stream) {
|
||||||
|
this._connectStream(this._stream);
|
||||||
|
this._updateVolume();
|
||||||
|
} else {
|
||||||
|
this.emit('stream-updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateVisibility();
|
||||||
|
},
|
||||||
|
|
||||||
|
_disconnectStream: function(stream) {
|
||||||
|
stream.disconnect(this._mutedChangedId);
|
||||||
|
this._mutedChangedId = 0;
|
||||||
|
stream.disconnect(this._volumeChangedId);
|
||||||
|
this._volumeChangedId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_connectStream: function(stream) {
|
||||||
|
this._mutedChangedId = stream.connect('notify::is-muted', Lang.bind(this, this._updateVolume));
|
||||||
|
this._volumeChangedId = stream.connect('notify::volume', Lang.bind(this, this._updateVolume));
|
||||||
|
},
|
||||||
|
|
||||||
|
_shouldBeVisible: function() {
|
||||||
|
return this._stream != null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateVisibility: function() {
|
||||||
|
let visible = this._shouldBeVisible();
|
||||||
|
this._title.actor.visible = visible;
|
||||||
|
this._slider.actor.visible = visible;
|
||||||
},
|
},
|
||||||
|
|
||||||
scroll: function(event) {
|
scroll: function(event) {
|
||||||
this._outputSlider.scroll(event);
|
this._slider.scroll(event);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onControlStateChanged: function() {
|
setValue: function(value) {
|
||||||
if (this._control.get_state() == Gvc.MixerControlState.READY) {
|
// piggy-back off of sliderChanged
|
||||||
this._readOutput();
|
this._slider.setValue(value);
|
||||||
this._readInput();
|
},
|
||||||
this._maybeShowInput();
|
|
||||||
|
_sliderChanged: function(slider, value, property) {
|
||||||
|
if (!this._stream)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let volume = value * this._control.get_vol_max_norm();
|
||||||
|
let prevMuted = this._stream.is_muted;
|
||||||
|
if (volume < 1) {
|
||||||
|
this._stream.volume = 0;
|
||||||
|
if (!prevMuted)
|
||||||
|
this._stream.change_is_muted(true);
|
||||||
} else {
|
} else {
|
||||||
this.emit('icon-changed');
|
this._stream.volume = volume;
|
||||||
|
if (prevMuted)
|
||||||
|
this._stream.change_is_muted(false);
|
||||||
}
|
}
|
||||||
|
this._stream.push_volume();
|
||||||
|
},
|
||||||
|
|
||||||
|
_notifyVolumeChange: function() {
|
||||||
|
global.cancel_theme_sound(VOLUME_NOTIFY_ID);
|
||||||
|
global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateVolume: function() {
|
||||||
|
let muted = this._stream.is_muted;
|
||||||
|
this._slider.setValue(muted ? 0 : (this._stream.volume / this._control.get_vol_max_norm()));
|
||||||
|
this.emit('stream-updated');
|
||||||
|
},
|
||||||
|
|
||||||
|
getIcon: function() {
|
||||||
|
if (!this._stream)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let volume = this._stream.volume;
|
||||||
|
if (this._stream.is_muted || volume <= 0) {
|
||||||
|
return 'audio-volume-muted-symbolic';
|
||||||
|
} else {
|
||||||
|
let n = Math.floor(3 * volume / this._control.get_vol_max_norm()) + 1;
|
||||||
|
if (n < 2)
|
||||||
|
return 'audio-volume-low-symbolic';
|
||||||
|
if (n >= 3)
|
||||||
|
return 'audio-volume-high-symbolic';
|
||||||
|
return 'audio-volume-medium-symbolic';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Signals.addSignalMethods(StreamSlider.prototype);
|
||||||
|
|
||||||
|
const OutputStreamSlider = new Lang.Class({
|
||||||
|
Name: 'OutputStreamSlider',
|
||||||
|
Extends: StreamSlider,
|
||||||
|
|
||||||
|
_connectStream: function(stream) {
|
||||||
|
this.parent(stream);
|
||||||
|
this._portChangedId = stream.connect('notify::port', Lang.bind(this, this._portChanged));
|
||||||
|
this._portChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
_findHeadphones: function(sink) {
|
_findHeadphones: function(sink) {
|
||||||
@ -99,58 +173,41 @@ const VolumeMenu = new Lang.Class({
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_disconnectStream: function(stream) {
|
||||||
|
this.parent(stream);
|
||||||
|
stream.disconnect(this._portChangedId);
|
||||||
|
this._portChangedId = 0;
|
||||||
|
},
|
||||||
|
|
||||||
_portChanged: function() {
|
_portChanged: function() {
|
||||||
this._hasHeadphones = this._findHeadphones(this._output);
|
let hasHeadphones = this._findHeadphones(this._stream);
|
||||||
this.emit('headphones-changed', this._hasHeadphones);
|
if (hasHeadphones != this._hasHeadphones) {
|
||||||
|
this._hasHeadphones = hasHeadphones;
|
||||||
|
this.emit('headphones-changed', this._hasHeadphones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const InputStreamSlider = new Lang.Class({
|
||||||
|
Name: 'InputStreamSlider',
|
||||||
|
Extends: StreamSlider,
|
||||||
|
|
||||||
|
_init: function(control, title) {
|
||||||
|
this.parent(control, title);
|
||||||
|
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
|
||||||
|
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
|
||||||
},
|
},
|
||||||
|
|
||||||
_readOutput: function() {
|
_connectStream: function(stream) {
|
||||||
if (this._outputVolumeId) {
|
this.parent(stream);
|
||||||
this._output.disconnect(this._outputVolumeId);
|
this._maybeShowInput();
|
||||||
this._output.disconnect(this._outputMutedId);
|
|
||||||
this._output.disconnect(this._outputPortId);
|
|
||||||
this._outputVolumeId = 0;
|
|
||||||
this._outputMutedId = 0;
|
|
||||||
this._outputPortId = 0;
|
|
||||||
}
|
|
||||||
this._output = this._control.get_default_sink();
|
|
||||||
if (this._output) {
|
|
||||||
this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._updateVolume, '_output'));
|
|
||||||
this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._updateVolume, '_output'));
|
|
||||||
this._outputPortId = this._output.connect('notify::port', Lang.bind(this, this._portChanged));
|
|
||||||
|
|
||||||
this._updateVolume(null, null, '_output');
|
|
||||||
this._portChanged();
|
|
||||||
} else {
|
|
||||||
this.hasHeadphones = false;
|
|
||||||
this._outputSlider.setValue(0);
|
|
||||||
this.emit('icon-changed');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_readInput: function() {
|
|
||||||
if (this._inputVolumeId) {
|
|
||||||
this._input.disconnect(this._inputVolumeId);
|
|
||||||
this._input.disconnect(this._inputMutedId);
|
|
||||||
this._inputVolumeId = 0;
|
|
||||||
this._inputMutedId = 0;
|
|
||||||
}
|
|
||||||
this._input = this._control.get_default_source();
|
|
||||||
if (this._input) {
|
|
||||||
this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._updateVolume, '_input'));
|
|
||||||
this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._updateVolume, '_input'));
|
|
||||||
this._updateVolume(null, null, '_input');
|
|
||||||
} else {
|
|
||||||
this._inputTitle.actor.hide();
|
|
||||||
this._inputSlider.actor.hide();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_maybeShowInput: function() {
|
_maybeShowInput: function() {
|
||||||
// only show input widgets if any application is recording audio
|
// only show input widgets if any application is recording audio
|
||||||
let showInput = false;
|
let showInput = false;
|
||||||
let recordingApps = this._control.get_source_outputs();
|
let recordingApps = this._control.get_source_outputs();
|
||||||
if (this._input && recordingApps) {
|
if (this._stream && recordingApps) {
|
||||||
for (let i = 0; i < recordingApps.length; i++) {
|
for (let i = 0; i < recordingApps.length; i++) {
|
||||||
let outputStream = recordingApps[i];
|
let outputStream = recordingApps[i];
|
||||||
let id = outputStream.get_application_id();
|
let id = outputStream.get_application_id();
|
||||||
@ -163,57 +220,70 @@ const VolumeMenu = new Lang.Class({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._inputTitle.actor.visible = showInput;
|
this._showInput = showInput;
|
||||||
this._inputSlider.actor.visible = showInput;
|
this._updateVisibility();
|
||||||
},
|
},
|
||||||
|
|
||||||
_sliderChanged: function(slider, value, property) {
|
_shouldBeVisible: function() {
|
||||||
if (this[property] == null) {
|
return this.parent() && this._showInput;
|
||||||
log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
|
}
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
let volume = value * this._volumeMax;
|
const VolumeMenu = new Lang.Class({
|
||||||
let prev_muted = this[property].is_muted;
|
Name: 'VolumeMenu',
|
||||||
if (volume < 1) {
|
Extends: PopupMenu.PopupMenuSection,
|
||||||
this[property].volume = 0;
|
|
||||||
if (!prev_muted)
|
_init: function(control) {
|
||||||
this[property].change_is_muted(true);
|
this.parent();
|
||||||
|
|
||||||
|
this.hasHeadphones = false;
|
||||||
|
|
||||||
|
this._control = control;
|
||||||
|
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
|
||||||
|
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
|
||||||
|
this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
|
||||||
|
|
||||||
|
/* Translators: This is the label for audio volume */
|
||||||
|
this._output = new OutputStreamSlider(this._control, _("Volume"));
|
||||||
|
this._output.connect('stream-updated', Lang.bind(this, function() {
|
||||||
|
this.emit('icon-changed');
|
||||||
|
}));
|
||||||
|
this._output.connect('headphones-changed', Lang.bind(this, function(stream, value) {
|
||||||
|
this.emit('headphones-changed', value);
|
||||||
|
}));
|
||||||
|
this.addMenuItem(this._output.item);
|
||||||
|
|
||||||
|
this._input = new InputStreamSlider(this._control, _("Microphone"));
|
||||||
|
this.addMenuItem(this._input.item);
|
||||||
|
|
||||||
|
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
|
|
||||||
|
this._onControlStateChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: function(event) {
|
||||||
|
this._output.scroll(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onControlStateChanged: function() {
|
||||||
|
if (this._control.get_state() == Gvc.MixerControlState.READY) {
|
||||||
|
this._readInput();
|
||||||
|
this._readOutput();
|
||||||
} else {
|
} else {
|
||||||
this[property].volume = volume;
|
this.emit('icon-changed');
|
||||||
if (prev_muted)
|
|
||||||
this[property].change_is_muted(false);
|
|
||||||
}
|
}
|
||||||
this[property].push_volume();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_notifyVolumeChange: function() {
|
_readOutput: function() {
|
||||||
global.cancel_theme_sound(VOLUME_NOTIFY_ID);
|
this._output.stream = this._control.get_default_sink();
|
||||||
global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
|
},
|
||||||
|
|
||||||
|
_readInput: function() {
|
||||||
|
this._input.stream = this._control.get_default_source();
|
||||||
},
|
},
|
||||||
|
|
||||||
getIcon: function() {
|
getIcon: function() {
|
||||||
if (!this._output)
|
return this._output.getIcon();
|
||||||
return null;
|
|
||||||
|
|
||||||
let volume = this._output.volume;
|
|
||||||
if (this._output.is_muted || volume <= 0) {
|
|
||||||
return 'audio-volume-muted-symbolic';
|
|
||||||
} else {
|
|
||||||
let n = Math.floor(3 * volume / this._volumeMax) + 1;
|
|
||||||
if (n < 2)
|
|
||||||
return 'audio-volume-low-symbolic';
|
|
||||||
if (n >= 3)
|
|
||||||
return 'audio-volume-high-symbolic';
|
|
||||||
return 'audio-volume-medium-symbolic';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateVolume: function(object, param_spec, property) {
|
|
||||||
let muted = this[property].is_muted;
|
|
||||||
let slider = this[property+'Slider'];
|
|
||||||
slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
|
|
||||||
if (property == '_output')
|
|
||||||
this.emit('icon-changed');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user