Add volume control plugin for ALSA.
[lxde/lxpanel.git] / src / plugins / volumealsa / volumealsa.c
1 /**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <gtk/gtk.h>
20 #include <stdlib.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 #include <gdk-pixbuf/gdk-pixbuf.h>
26 #include <alsa/asoundlib.h>
27 #include "panel.h"
28 #include "misc.h"
29 #include "plugin.h"
30 #include "dbg.h"
31
32 #define ICONS_VOLUME "/usr/share/lxpanel/images/volume.png"
33 #define ICONS_MUTE "/usr/share/lxpanel/images/mute.png"
34
35 typedef struct {
36 GtkWidget *mainw;
37 GtkWidget *tray_icon;
38 GtkWidget *dlg;
39 GtkTooltips* tooltips;
40 GtkWidget *vscale;
41 snd_mixer_t *mixer;
42 snd_mixer_selem_id_t *sid;
43 snd_mixer_elem_t *master_element;
44 long alsa_min_vol, alsa_max_vol;
45 int mute;
46 int show;
47 } volume_t;
48
49
50
51 /* ALSA */
52 static gboolean find_element(volume_t *vol, const char *ename)
53 {
54 for (vol->master_element=snd_mixer_first_elem(vol->mixer);vol->master_element;vol->master_element=snd_mixer_elem_next(vol->master_element)) {
55 snd_mixer_selem_get_id(vol->master_element, vol->sid);
56 if (!snd_mixer_selem_is_active(vol->master_element))
57 continue;
58
59 if (strcmp(ename, snd_mixer_selem_id_get_name(vol->sid))==0) {
60 return TRUE;
61 }
62 }
63
64 return FALSE;
65 }
66
67 static void asound_init(volume_t *vol)
68 {
69 snd_mixer_selem_id_alloca(&vol->sid);
70 snd_mixer_open(&vol->mixer, 0);
71 snd_mixer_attach(vol->mixer, "default");
72 snd_mixer_selem_register(vol->mixer, NULL, NULL);
73 snd_mixer_load(vol->mixer);
74
75 /* Find Master element */
76 if (!find_element(vol, "Master"))
77 if (!find_element(vol, "Front"))
78 if (!find_element(vol, "PCM"))
79 exit;
80
81
82 snd_mixer_selem_get_playback_volume_range(vol->master_element, &vol->alsa_min_vol, &vol->alsa_max_vol);
83
84 snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);
85 }
86
87 static int asound_read(volume_t *vol)
88 {
89 long aleft, aright;
90 snd_mixer_handle_events(vol->mixer);
91 /* Left */
92 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
93 /* Right */
94 snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);
95
96 return (aleft + aright) >> 1;
97 }
98
99 static void asound_write(volume_t *vol, int volume)
100 {
101 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
102 snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
103 }
104
105 static gboolean focus_out_event(GtkWidget *widget, GdkEvent *event, volume_t *vol)
106 {
107 gtk_widget_hide(vol->dlg);
108 return FALSE;
109 }
110
111 static void tray_icon_press(GtkWidget *widget, GdkEvent *event, volume_t *vol)
112 {
113 if (vol->show==0) {
114 gtk_window_set_position(GTK_WINDOW(vol->dlg), GTK_WIN_POS_MOUSE);
115 gtk_scale_set_digits(GTK_SCALE(vol->vscale), asound_read(vol));
116 gtk_widget_show_all(vol->dlg);
117 vol->show = 1;
118 } else {
119 gtk_widget_hide(vol->dlg);
120 vol->show = 0;
121 }
122 }
123
124 static void on_vscale_value_changed(GtkRange *range, volume_t *vol)
125 {
126 asound_write(vol, gtk_range_get_value(range));
127 }
128
129 static void click_mute(GtkWidget *widget, volume_t *vol)
130 {
131 int chn;
132
133 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
134 gtk_image_set_from_file(vol->tray_icon, ICONS_MUTE);
135 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
136 snd_mixer_selem_set_playback_switch(vol->master_element, chn, 0);
137 }
138 } else {
139 gtk_image_set_from_file(vol->tray_icon, ICONS_VOLUME);
140 for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
141 snd_mixer_selem_set_playback_switch(vol->master_element, chn, 1);
142 }
143 }
144 }
145
146 static void panel_init(plugin *p)
147 {
148 volume_t *vol = p->priv;
149 GtkWidget *scrolledwindow;
150 GtkWidget *viewport;
151 GtkWidget *box;
152 GtkWidget *frame;
153 GtkWidget *checkbutton;
154
155 /* set show flags */
156 vol->show = 0;
157
158 /* create a new window */
159 vol->dlg = gtk_window_new(GTK_WINDOW_TOPLEVEL);
160 gtk_window_set_decorated(GTK_WINDOW(vol->dlg), FALSE);
161 gtk_container_set_border_width(GTK_CONTAINER(vol->dlg), 3);
162 gtk_window_set_default_size(GTK_WINDOW(vol->dlg), 80, 140);
163 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->dlg), TRUE);
164 gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->dlg), TRUE);
165 gtk_window_set_type_hint(GTK_WINDOW(vol->dlg), GDK_WINDOW_TYPE_HINT_DIALOG);
166
167 /* setting background to default */
168 gtk_widget_set_style(vol->dlg, p->panel->defstyle);
169
170 /* Focus-out signal */
171 g_signal_connect (G_OBJECT (vol->dlg), "focus_out_event",
172 G_CALLBACK (focus_out_event), vol);
173
174 scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
175 gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow), 1);
176 gtk_widget_show (scrolledwindow);
177 gtk_container_add (GTK_CONTAINER (vol->dlg), scrolledwindow);
178 GTK_WIDGET_UNSET_FLAGS (scrolledwindow, GTK_CAN_FOCUS);
179 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
180 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_OUT);
181
182 viewport = gtk_viewport_new (NULL, NULL);
183 gtk_widget_show (viewport);
184 gtk_container_add (GTK_CONTAINER (scrolledwindow), viewport);
185 gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
186 gtk_widget_show(viewport);
187
188 /* create frame */
189 frame = gtk_frame_new(_("Volume"));
190 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
191 gtk_container_add(GTK_CONTAINER(viewport), frame);
192
193 /* create box */
194 box = gtk_vbox_new(FALSE, 0);
195
196 /* create controller */
197 vol->vscale = gtk_vscale_new(GTK_ADJUSTMENT(gtk_adjustment_new(asound_read(vol), 0, 100, 0, 0, 0)));
198 gtk_scale_set_draw_value(GTK_SCALE(vol->vscale), FALSE);
199 gtk_range_set_inverted(GTK_RANGE(vol->vscale), TRUE);
200
201 g_signal_connect ((gpointer) vol->vscale, "value_changed",
202 G_CALLBACK (on_vscale_value_changed),
203 vol);
204
205 checkbutton = gtk_check_button_new_with_label(_("Mute"));
206 snd_mixer_selem_get_playback_switch(vol->master_element, 0, &vol->mute);
207
208 if (!vol->mute)
209 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton), TRUE);
210
211 g_signal_connect ((gpointer) checkbutton, "toggled",
212 G_CALLBACK (click_mute),
213 vol);
214
215 gtk_box_pack_start(GTK_BOX(box), vol->vscale, TRUE, TRUE, 0);
216 gtk_box_pack_end(GTK_BOX(box), checkbutton, FALSE, FALSE, 0);
217 gtk_container_add(GTK_CONTAINER(frame), box);
218
219 /* setting background to default */
220 gtk_widget_set_style(viewport, p->panel->defstyle);
221 }
222
223 static void
224 volumealsa_destructor(plugin *p)
225 {
226 volume_t *vol = (volume_t *) p->priv;
227
228 ENTER;
229 if (vol->dlg)
230 gtk_widget_destroy(vol->dlg);
231 g_object_unref( vol->tooltips );
232 gtk_widget_destroy(vol->mainw);
233 g_free(vol);
234 RET();
235 }
236
237 static int
238 volumealsa_constructor(plugin *p, char **fp)
239 {
240 volume_t *vol;
241 line s;
242 GdkPixbuf *icon;
243 GtkWidget *image;
244 GtkIconTheme* theme;
245 GtkIconInfo* info;
246
247 ENTER;
248 s.len = 256;
249 vol = g_new0(volume_t, 1);
250 g_return_val_if_fail(vol != NULL, 0);
251 p->priv = vol;
252
253 /* initializing */
254 asound_init(vol);
255 panel_init(p);
256
257 /* main */
258 vol->mainw = gtk_event_box_new();
259
260 gtk_widget_add_events(vol->mainw, GDK_BUTTON_PRESS_MASK);
261 gtk_widget_set_size_request( vol->mainw, 24, 24 );
262 gtk_container_add(GTK_CONTAINER(p->pwid), vol->mainw);
263 g_signal_connect(G_OBJECT(vol->mainw), "button-press-event",
264 G_CALLBACK(tray_icon_press), vol);
265
266 /* tray icon */
267 snd_mixer_selem_get_playback_switch(vol->master_element, 0, &vol->mute);
268 if (vol->mute==0)
269 vol->tray_icon = gtk_image_new_from_file(ICONS_MUTE);
270 else
271 vol->tray_icon = gtk_image_new_from_file(ICONS_VOLUME);
272
273
274 gtk_container_add(GTK_CONTAINER(vol->mainw), vol->tray_icon);
275
276 gtk_widget_show_all(vol->mainw);
277
278 vol->tooltips = gtk_tooltips_new ();
279 #if GLIB_CHECK_VERSION( 2, 10, 0 )
280 g_object_ref_sink( vol->tooltips );
281 #else
282 g_object_ref( vol->tooltips );
283 gtk_object_sink( vol->tooltips );
284 #endif
285
286 /* FIXME: display current level in tooltip. ex: "Volume Control: 80%" */
287 gtk_tooltips_set_tip (vol->tooltips, vol->mainw, _("Volume control"), NULL);
288
289 RET(1);
290 }
291
292
293 plugin_class volumealsa_plugin_class = {
294 fname: NULL,
295 count: 0,
296
297 type : "volumealsa",
298 name : N_("Volume Control"),
299 version: "1.0",
300 description : "Display and control volume for ALSA",
301
302 constructor : volumealsa_constructor,
303 destructor : volumealsa_destructor,
304 config : NULL,
305 save : NULL
306 };