2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2010-07-23 00:39:44 +00:00
|
|
|
|
2010-10-20 19:53:23 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2010-07-23 00:39:44 +00:00
|
|
|
const Lang = imports.lang;
|
|
|
|
const Gvc = imports.gi.Gvc;
|
|
|
|
const St = imports.gi.St;
|
|
|
|
|
|
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
|
|
|
2010-10-20 19:53:23 +00:00
|
|
|
const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
|
2010-07-23 00:39:44 +00:00
|
|
|
|
2011-02-15 18:23:36 +00:00
|
|
|
const VOLUME_NOTIFY_ID = 1;
|
|
|
|
|
2012-08-26 14:05:46 +00:00
|
|
|
// Each Gvc.MixerControl is a connection to PulseAudio,
|
|
|
|
// so it's better to make it a singleton
|
|
|
|
let _mixerControl;
|
|
|
|
function getMixerControl() {
|
|
|
|
if (_mixerControl)
|
|
|
|
return _mixerControl;
|
2010-07-23 00:39:44 +00:00
|
|
|
|
2012-08-26 14:05:46 +00:00
|
|
|
_mixerControl = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
|
|
|
|
_mixerControl.open();
|
|
|
|
|
|
|
|
return _mixerControl;
|
|
|
|
}
|
2010-07-23 00:39:44 +00:00
|
|
|
|
2012-08-26 14:05:46 +00:00
|
|
|
const VolumeMenu = new Lang.Class({
|
|
|
|
Name: 'VolumeMenu',
|
|
|
|
Extends: PopupMenu.PopupMenuSection,
|
|
|
|
|
|
|
|
_init: function(control) {
|
|
|
|
this.parent();
|
|
|
|
|
|
|
|
this._control = control;
|
2011-03-28 14:10:11 +00:00
|
|
|
this._control.connect('state-changed', Lang.bind(this, this._onControlStateChanged));
|
2010-07-23 00:39:44 +00:00
|
|
|
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));
|
2011-03-11 15:45:11 +00:00
|
|
|
this._volumeMax = this._control.get_vol_max_norm();
|
2010-07-23 00:39:44 +00:00
|
|
|
|
|
|
|
this._output = null;
|
|
|
|
this._outputVolumeId = 0;
|
|
|
|
this._outputMutedId = 0;
|
2011-09-07 12:11:10 +00:00
|
|
|
/* Translators: This is the label for audio volume */
|
2010-11-16 14:22:38 +00:00
|
|
|
this._outputTitle = new PopupMenu.PopupMenuItem(_("Volume"), { reactive: false });
|
2011-07-19 23:45:29 +00:00
|
|
|
this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
2010-07-23 00:39:44 +00:00
|
|
|
this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
|
|
|
|
this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
2012-08-26 14:05:46 +00:00
|
|
|
this.addMenuItem(this._outputTitle);
|
|
|
|
this.addMenuItem(this._outputSlider);
|
2010-07-23 00:39:44 +00:00
|
|
|
|
2012-08-26 14:05:46 +00:00
|
|
|
this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
2010-07-23 00:39:44 +00:00
|
|
|
|
|
|
|
this._input = null;
|
|
|
|
this._inputVolumeId = 0;
|
|
|
|
this._inputMutedId = 0;
|
2010-11-16 14:22:38 +00:00
|
|
|
this._inputTitle = new PopupMenu.PopupMenuItem(_("Microphone"), { reactive: false });
|
2011-07-19 23:45:29 +00:00
|
|
|
this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
2010-07-23 00:39:44 +00:00
|
|
|
this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
|
|
|
|
this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
2012-08-26 14:05:46 +00:00
|
|
|
this.addMenuItem(this._inputTitle);
|
|
|
|
this.addMenuItem(this._inputSlider);
|
2012-05-22 22:27:06 +00:00
|
|
|
},
|
|
|
|
|
2012-08-26 14:05:46 +00:00
|
|
|
scroll: function(direction) {
|
2010-10-20 19:53:23 +00:00
|
|
|
let currentVolume = this._output.volume;
|
|
|
|
|
|
|
|
if (direction == Clutter.ScrollDirection.DOWN) {
|
2010-11-16 14:22:38 +00:00
|
|
|
let prev_muted = this._output.is_muted;
|
2011-08-31 10:46:57 +00:00
|
|
|
this._output.volume = Math.max(0, currentVolume - this._volumeMax * VOLUME_ADJUSTMENT_STEP);
|
2010-11-16 14:22:38 +00:00
|
|
|
if (this._output.volume < 1) {
|
|
|
|
this._output.volume = 0;
|
|
|
|
if (!prev_muted)
|
|
|
|
this._output.change_is_muted(true);
|
|
|
|
}
|
2010-10-20 19:53:23 +00:00
|
|
|
this._output.push_volume();
|
|
|
|
}
|
|
|
|
else if (direction == Clutter.ScrollDirection.UP) {
|
2011-08-31 10:46:57 +00:00
|
|
|
this._output.volume = Math.min(this._volumeMax, currentVolume + this._volumeMax * VOLUME_ADJUSTMENT_STEP);
|
2010-11-16 14:22:38 +00:00
|
|
|
this._output.change_is_muted(false);
|
2010-10-20 19:53:23 +00:00
|
|
|
this._output.push_volume();
|
|
|
|
}
|
2011-02-15 18:23:36 +00:00
|
|
|
|
|
|
|
this._notifyVolumeChange();
|
2010-10-20 19:53:23 +00:00
|
|
|
},
|
|
|
|
|
2011-03-28 14:10:11 +00:00
|
|
|
_onControlStateChanged: function() {
|
|
|
|
if (this._control.get_state() == Gvc.MixerControlState.READY) {
|
|
|
|
this._readOutput();
|
|
|
|
this._readInput();
|
|
|
|
} else {
|
2012-08-26 14:05:46 +00:00
|
|
|
this.emit('icon-changed', null);
|
2011-03-28 14:10:11 +00:00
|
|
|
}
|
2010-07-23 00:39:44 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_readOutput: function() {
|
|
|
|
if (this._outputVolumeId) {
|
|
|
|
this._output.disconnect(this._outputVolumeId);
|
|
|
|
this._output.disconnect(this._outputMutedId);
|
|
|
|
this._outputVolumeId = 0;
|
|
|
|
this._outputMutedId = 0;
|
|
|
|
}
|
|
|
|
this._output = this._control.get_default_sink();
|
|
|
|
if (this._output) {
|
|
|
|
this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output'));
|
|
|
|
this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output'));
|
|
|
|
this._mutedChanged (null, null, '_output');
|
|
|
|
this._volumeChanged (null, null, '_output');
|
|
|
|
} else {
|
2010-11-16 14:22:38 +00:00
|
|
|
this._outputSlider.setValue(0);
|
2012-08-26 14:05:46 +00:00
|
|
|
this.emit('icon-changed', 'audio-volume-muted-symbolic');
|
2010-07-23 00:39:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_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._mutedChanged, '_input'));
|
|
|
|
this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input'));
|
|
|
|
this._mutedChanged (null, null, '_input');
|
|
|
|
this._volumeChanged (null, null, '_input');
|
|
|
|
} else {
|
2010-11-16 14:22:38 +00:00
|
|
|
this._inputTitle.actor.hide();
|
2010-07-23 00:39:44 +00:00
|
|
|
this._inputSlider.actor.hide();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_maybeShowInput: function() {
|
|
|
|
// only show input widgets if any application is recording audio
|
|
|
|
let showInput = false;
|
|
|
|
let recordingApps = this._control.get_source_outputs();
|
2010-10-20 19:15:24 +00:00
|
|
|
if (this._input && recordingApps) {
|
|
|
|
for (let i = 0; i < recordingApps.length; i++) {
|
|
|
|
let outputStream = recordingApps[i];
|
2010-07-23 00:39:44 +00:00
|
|
|
let id = outputStream.get_application_id();
|
|
|
|
// but skip gnome-volume-control and pavucontrol
|
|
|
|
// (that appear as recording because they show the input level)
|
|
|
|
if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) {
|
|
|
|
showInput = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-16 22:53:14 +00:00
|
|
|
|
|
|
|
this._inputTitle.actor.visible = showInput;
|
|
|
|
this._inputSlider.actor.visible = showInput;
|
2010-07-23 00:39:44 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_volumeToIcon: function(volume) {
|
|
|
|
if (volume <= 0) {
|
2012-05-30 13:58:37 +00:00
|
|
|
return 'audio-volume-muted-symbolic';
|
2010-07-23 00:39:44 +00:00
|
|
|
} else {
|
2011-08-31 10:46:57 +00:00
|
|
|
let n = Math.floor(3 * volume / this._volumeMax) + 1;
|
2011-02-05 18:42:50 +00:00
|
|
|
if (n < 2)
|
2012-05-30 13:58:37 +00:00
|
|
|
return 'audio-volume-low-symbolic';
|
2011-02-05 18:42:50 +00:00
|
|
|
if (n >= 3)
|
2012-05-30 13:58:37 +00:00
|
|
|
return 'audio-volume-high-symbolic';
|
|
|
|
return 'audio-volume-medium-symbolic';
|
2010-07-23 00:39:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_sliderChanged: function(slider, value, property) {
|
|
|
|
if (this[property] == null) {
|
|
|
|
log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
|
|
|
|
return;
|
|
|
|
}
|
2011-08-31 10:46:57 +00:00
|
|
|
let volume = value * this._volumeMax;
|
2010-11-16 14:22:38 +00:00
|
|
|
let prev_muted = this[property].is_muted;
|
|
|
|
if (volume < 1) {
|
|
|
|
this[property].volume = 0;
|
|
|
|
if (!prev_muted)
|
|
|
|
this[property].change_is_muted(true);
|
|
|
|
} else {
|
|
|
|
this[property].volume = volume;
|
|
|
|
if (prev_muted)
|
|
|
|
this[property].change_is_muted(false);
|
|
|
|
}
|
2010-07-23 00:39:44 +00:00
|
|
|
this[property].push_volume();
|
|
|
|
},
|
|
|
|
|
|
|
|
_notifyVolumeChange: function() {
|
2011-02-15 18:23:36 +00:00
|
|
|
global.cancel_theme_sound(VOLUME_NOTIFY_ID);
|
|
|
|
global.play_theme_sound(VOLUME_NOTIFY_ID, 'audio-volume-change');
|
2010-07-23 00:39:44 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_mutedChanged: function(object, param_spec, property) {
|
|
|
|
let muted = this[property].is_muted;
|
2010-11-16 14:22:38 +00:00
|
|
|
let slider = this[property+'Slider'];
|
2011-08-31 10:46:57 +00:00
|
|
|
slider.setValue(muted ? 0 : (this[property].volume / this._volumeMax));
|
2010-07-23 00:39:44 +00:00
|
|
|
if (property == '_output') {
|
|
|
|
if (muted)
|
2012-05-30 13:58:37 +00:00
|
|
|
this.emit('icon-changed', 'audio-volume-muted-symbolic');
|
2010-07-23 00:39:44 +00:00
|
|
|
else
|
2012-08-26 14:05:46 +00:00
|
|
|
this.emit('icon-changed', this._volumeToIcon(this._output.volume));
|
2010-07-23 00:39:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_volumeChanged: function(object, param_spec, property) {
|
2011-08-31 10:46:57 +00:00
|
|
|
this[property+'Slider'].setValue(this[property].volume / this._volumeMax);
|
2010-10-22 13:31:58 +00:00
|
|
|
if (property == '_output' && !this._output.is_muted)
|
2012-08-26 14:05:46 +00:00
|
|
|
this.emit('icon-changed', this._volumeToIcon(this._output.volume));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const Indicator = new Lang.Class({
|
|
|
|
Name: 'VolumeIndicator',
|
|
|
|
Extends: PanelMenu.SystemStatusButton,
|
|
|
|
|
|
|
|
_init: function() {
|
2012-08-30 19:53:25 +00:00
|
|
|
this.parent('audio-volume-muted-symbolic', _("Volume"));
|
2012-08-26 14:05:46 +00:00
|
|
|
|
|
|
|
this._control = getMixerControl();
|
|
|
|
this._volumeMenu = new VolumeMenu(this._control);
|
|
|
|
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
|
|
|
|
this._hasPulseAudio = (icon != null);
|
|
|
|
this.setIcon(icon);
|
|
|
|
this._syncVisibility();
|
|
|
|
}));
|
|
|
|
|
|
|
|
this.menu.addMenuItem(this._volumeMenu);
|
|
|
|
|
|
|
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
|
|
|
this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
|
|
|
|
|
|
|
|
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
|
|
|
},
|
|
|
|
|
|
|
|
_syncVisibility: function() {
|
2012-09-01 12:42:53 +00:00
|
|
|
this.actor.visible = this._hasPulseAudio;
|
2012-08-26 14:05:46 +00:00
|
|
|
this.mainIcon.visible = this._hasPulseAudio;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onScrollEvent: function(actor, event) {
|
|
|
|
this._volumeMenu.scroll(event.get_scroll_direction());
|
2010-07-23 00:39:44 +00:00
|
|
|
}
|
2011-11-20 14:38:48 +00:00
|
|
|
});
|