4 // Advanced Volume Mixer
5 // Control programs' volume from gnome volume mixer applet.
7 // Author: Harry Karvonen <harry.karvonen@gmail.com>
10 const Clutter = imports.gi.Clutter;
11 const Lang = imports.lang;
12 const Gvc = imports.gi.Gvc;
13 const Signals = imports.signals;
14 const St = imports.gi.St;
16 const Main = imports.ui.main;
17 const PopupMenu = imports.ui.popupMenu;
23 function AdvPopupSwitchMenuItem() {
24 this._init.apply(this, arguments);
28 AdvPopupSwitchMenuItem.prototype = {
29 __proto__: PopupMenu.PopupSwitchMenuItem.prototype,
31 _init: function(text, active, gicon, params) {
32 PopupMenu.PopupSwitchMenuItem.prototype._init.call(
39 this._icon = new St.Icon({
41 style_class: "adv-volume-icon"
45 this.removeActor(this._statusBin);
46 this.removeActor(this.label)
49 let labelBox = new St.BoxLayout({vertical: false});
51 labelBox.add(this._icon,
52 {expand: false, x_fill: false, x_align: St.Align.START});
53 labelBox.add(this.label,
54 {expand: false, x_fill: false, x_align: St.Align.START});
55 labelBox.add(this._statusBin,
56 {expand: true, x_fill: true, x_align: St.Align.END});
58 this.addActor(labelBox, {span: -1, expand: true });
63 function AdvMixer(mixer) {
68 AdvMixer.prototype = {
69 _init: function(mixer) {
71 this._control = mixer._control;
72 this._separator = new PopupMenu.PopupSeparatorMenuItem();
75 this._outputMenu = new PopupMenu.PopupSubMenuMenuItem(_("Volume"));
77 this._mixer.menu.addMenuItem(this._separator, 1);
79 this._streamAddedId = this._control.connect(
81 Lang.bind(this, this._streamAdded)
83 this._streamRemovedId = this._control.connect(
85 Lang.bind(this, this._streamRemoved)
87 this._defaultSinkChangedId = this._control.connect(
88 "default-sink-changed",
89 Lang.bind(this, this._defaultSinkChanged)
92 // Change Volume label
93 let label = this._mixer.menu.firstMenuItem;
97 this._mixer.menu.addMenuItem(this._outputMenu, 0);
98 this._outputMenu.actor.show();
101 let streams = this._control.get_streams();
102 for (let i = 0; i < streams.length; i++) {
103 this._streamAdded(this._control, streams[i].id);
106 if (this._control.get_default_sink() != null) {
107 this._defaultSinkChanged(
109 this._control.get_default_sink().id
115 _streamAdded: function(control, id) {
116 if (id in this._items) {
120 if (id in this._outputs) {
124 let stream = control.lookup_stream_id(id);
126 if (stream["is-event-stream"]) {
128 } else if (stream instanceof Gvc.MixerSinkInput) {
129 let slider = new PopupMenu.PopupSliderMenuItem(
130 stream.volume / this._control.get_vol_max_norm()
132 let title = new AdvPopupSwitchMenuItem(
133 stream.name || stream.description,
146 Lang.bind(this, this._sliderValueChanged, stream.id)
150 "button-release-event",
151 Lang.bind(this, this._titleToggleState, stream.id)
156 Lang.bind(this, this._titleToggleState, stream.id)
161 Lang.bind(this, this._notifyVolume, stream.id)
166 Lang.bind(this, this._notifyIsMuted, stream.id)
169 this._mixer.menu.addMenuItem(this._items[id]["slider"], 2);
170 this._mixer.menu.addMenuItem(this._items[id]["title"], 2);
171 } else if (stream instanceof Gvc.MixerSink) {
172 let output = new PopupMenu.PopupMenuItem(stream.description);
176 function (item, event) { control.set_default_sink(stream); }
179 this._outputMenu.menu.addMenuItem(output);
181 this._outputs[id] = output;
185 _streamRemoved: function(control, id) {
186 if (id in this._items) {
187 this._items[id]["slider"].destroy();
188 this._items[id]["title"].destroy();
189 delete this._items[id];
192 if (id in this._outputs) {
193 this._outputs[id].destroy();
194 delete this._outputs[id];
198 _defaultSinkChanged: function(control, id) {
199 for (let output in this._outputs) {
200 this._outputs[output].setShowDot(output == id);
204 _sliderValueChanged: function(slider, value, id) {
205 let stream = this._control.lookup_stream_id(id);
206 let volume = value * this._control.get_vol_max_norm();
208 stream.volume = volume;
209 stream.push_volume();
212 _titleToggleState: function(title, event, id) {
213 if (event.type() == Clutter.EventType.KEY_PRESS) {
214 let symbol = event.get_key_symbol();
216 if (symbol != Clutter.KEY_space && symbol != Clutter.KEY_Return) {
221 let stream = this._control.lookup_stream_id(id);
223 stream.change_is_muted(!stream.is_muted);
228 _notifyVolume: function(object, param_spec, id) {
229 let stream = this._control.lookup_stream_id(id);
231 this._items[id]["slider"].setValue(stream.volume / this._control.get_vol_max_norm());
234 _notifyIsMuted: function(object, param_spec, id) {
235 let stream = this._control.lookup_stream_id(id);
237 this._items[id]["title"].setToggleState(!stream.is_muted);
240 destroy: function() {
241 this._control.disconnect(this._streamAddedId);
242 this._control.disconnect(this._streamRemovedId);
243 this._control.disconnect(this._defaultSinkChangedId);
245 this._separator.destroy();
246 delete this._separator;
248 // Restore Volume label
249 this._outputMenu.destroy();
250 delete this._outputMenu;
252 let label = new PopupMenu.PopupMenuItem(_("Volume"), {reactive: false });
253 this._mixer.menu.addMenuItem(label, 0);
256 // remove application streams
257 for (let id in this._items) {
258 this._streamRemoved(this._control, id);
261 this.emit("destroy");
266 Signals.addSignalMethods(AdvMixer.prototype);
280 if (Main.panel.statusArea['volume'] && !advMixer) {
281 advMixer = new AdvMixer(Main.panel.statusArea["volume"]);