4 // Advanced Volume Mixer
5 // Control programs' volume from gnome volume mixer applet.
7 // Idea from: https://extensions.gnome.org/extension/142/output-device-chooser-on-volume-menu/
9 // Author: Harry Karvonen <harry.karvonen@gmail.com>
12 const Clutter = imports.gi.Clutter;
13 const Lang = imports.lang;
14 const Gvc = imports.gi.Gvc;
15 const Signals = imports.signals;
16 const St = imports.gi.St;
18 const Main = imports.ui.main;
19 const PopupMenu = imports.ui.popupMenu;
25 function AdvPopupSwitchMenuItem() {
26 this._init.apply(this, arguments);
30 AdvPopupSwitchMenuItem.prototype = {
31 __proto__: PopupMenu.PopupSwitchMenuItem.prototype,
33 _init: function(text, active, gicon, params) {
34 PopupMenu.PopupSwitchMenuItem.prototype._init.call(
41 this._icon = new St.Icon({
43 style_class: "adv-volume-icon"
46 this.removeActor(this._statusBin);
47 this.removeActor(this.label)
49 this.label.add_style_class_name("adv-volume-label");
51 this.addActor(this._icon, {span: 0, expand: false});
52 this.addActor(this.label, {span: 2, expand: true});
53 this.addActor(this._statusBin, {span: -1,
55 align: St.Align.END});
60 function AdvMixer(mixer) {
65 AdvMixer.prototype = {
66 _init: function(mixer) {
68 this._control = mixer._control;
71 this._streamAddedId = this._control.connect(
73 Lang.bind(this, this._streamAdded)
75 this._streamRemovedId = this._control.connect(
77 Lang.bind(this, this._streamRemoved)
82 _streamAdded: function(control, id) {
83 if (id in this._items) {
87 let stream = control.lookup_stream_id(id);
89 if (stream["is-event-stream"]) {
91 } else if (stream instanceof Gvc.MixerSinkInput) {
92 let slider = new PopupMenu.PopupSliderMenuItem(
93 stream.volume / this._control.get_vol_max_norm()
95 let title = new AdvPopupSwitchMenuItem(
109 Lang.bind(this, this._sliderValueChanged, stream.id)
113 "button-release-event",
114 Lang.bind(this, this._titleToggleState, stream.id)
119 Lang.bind(this, this._titleToggleState, stream.id)
124 Lang.bind(this, this._notifyVolume, stream.id)
129 Lang.bind(this, this._notifyIsMuted, stream.id)
132 this._mixer.menu.addMenuItem(this._items[id]["slider"], 3);
133 this._mixer.menu.addMenuItem(this._items[id]["title"], 3);
137 _streamRemoved: function(control, id) {
138 if (id in this._items) {
139 this._items[id]["slider"].destroy();
140 this._items[id]["title"].destroy();
141 delete this._items[id];
145 _sliderValueChanged: function(slider, value, id) {
146 let stream = this._control.lookup_stream_id(id);
147 let volume = value * this._control.get_vol_max_norm();
149 stream.volume = volume;
150 stream.push_volume();
153 _titleToggleState: function(title, event, id) {
154 if (event.type() == Clutter.EventType.KEY_PRESS) {
155 let symbol = event.get_key_symbol();
157 if (symbol != Clutter.KEY_space && symbol != Clutter.KEY_Return) {
162 let stream = this._control.lookup_stream_id(id);
164 stream.change_is_muted(!stream.is_muted);
169 _notifyVolume: function(object, param_spec, id) {
170 let stream = this._control.lookup_stream_id(id);
172 this._items[id]["slider"].setValue(stream.volume / this._control.get_vol_max_norm());
175 _notifyIsMuted: function(object, param_spec, id) {
176 let stream = this._control.lookup_stream_id(id);
178 this._items[id]["title"].setToggleState(!stream.is_muted);
181 destroy: function() {
182 this._control.disconnect(this._streamAddedId);
183 this._control.disconnect(this._streamRemovedId);
184 this.emit("destroy");
189 Signals.addSignalMethods(AdvMixer.prototype);
203 if (Main.panel._statusArea['volume'] && !advMixer) {
204 advMixer = new AdvMixer(Main.panel._statusArea["volume"]);