Added stylesheet.css to depoy target
[AdvancedVolumeMixer.git] / extension.js
blob8b4ed6c47e085e58a1d55cd0053311316d932903
1 // extension.js
2 // vi: et sw=2
3 //
4 // Advanced Volume Mixer
5 // Control programs' volume from gnome volume mixer applet.
6 //
7 // Idea from: https://extensions.gnome.org/extension/142/output-device-chooser-on-volume-menu/
8 //
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;
22 let advMixer;
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(
35       this,
36       "   " + text,
37       active,
38       params
39     );
41     this._icon = new St.Icon({
42       gicon:        gicon,
43       style_class: "adv-volume-icon"
44     });
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,
54                                     expand: false,
55                                     align: St.Align.END});
56   }
60 function AdvMixer(mixer) {
61   this._init(mixer);
65 AdvMixer.prototype = {
66   _init: function(mixer) {
67     this._mixer = mixer;
68     this._control = mixer._control;
69     this._items = {};
71     this._streamAddedId = this._control.connect(
72       "stream-added",
73       Lang.bind(this, this._streamAdded)
74     );
75     this._streamRemovedId = this._control.connect(
76       "stream-removed",
77       Lang.bind(this, this._streamRemoved)
78     );
79   },
82   _streamAdded: function(control, id) {
83     if (id in this._items) {
84       return;
85     }
87     let stream = control.lookup_stream_id(id);
89     if (stream["is-event-stream"]) {
90       // Do nothing
91     } else if (stream instanceof Gvc.MixerSinkInput) {
92       let slider = new PopupMenu.PopupSliderMenuItem(
93         stream.volume / this._control.get_vol_max_norm()
94       );
95       let title = new AdvPopupSwitchMenuItem(
96         stream.name,
97         !stream.is_muted,
98         stream.get_gicon(),
99         {activate: false}
100       );
102       this._items[id] = {
103         slider: slider,
104         title: title
105       };
107       slider.connect(
108         "value-changed",
109         Lang.bind(this, this._sliderValueChanged, stream.id)
110       );
112       title.actor.connect(
113         "button-release-event",
114         Lang.bind(this, this._titleToggleState, stream.id)
115       );
117       title.actor.connect(
118         "key-press-event",
119         Lang.bind(this, this._titleToggleState, stream.id)
120       );
122       stream.connect(
123         "notify::volume",
124         Lang.bind(this, this._notifyVolume, stream.id)
125       );
127       stream.connect(
128         "notify::is-muted",
129         Lang.bind(this, this._notifyIsMuted, stream.id)
130       );
132       this._mixer.menu.addMenuItem(this._items[id]["slider"], 3);
133       this._mixer.menu.addMenuItem(this._items[id]["title"], 3);
134     }
135   },
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];
142     }
143   },
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();
151   },
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) {
158         return false;
159       }
160     }
162     let stream = this._control.lookup_stream_id(id);
164     stream.change_is_muted(!stream.is_muted);
166     return true;
167   },
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());
173   },
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);
179   },
181   destroy: function() {
182     this._control.disconnect(this._streamAddedId);
183     this._control.disconnect(this._streamRemovedId);
184     this.emit("destroy");
185   }
189 Signals.addSignalMethods(AdvMixer.prototype);
192 function main() {
193   init();
194   enable();
198 function init() {
202 function enable() {
203   if (Main.panel._statusArea['volume'] && !advMixer) {
204     advMixer = new AdvMixer(Main.panel._statusArea["volume"]);
205   }
209 function disable() {
210   if (advMixer) {
211     advMixer.destroy();
212     advMixer = null;
213   }