source: trunk/third/gnome-applets/mixer/mixer.c @ 21373

Revision 21373, 50.5 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21372, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3/*
4 * Mixer (volume control) applet.
5 *
6 * (C) Copyright 2001, Richard Hult
7 *
8 * Author: Richard Hult <richard@imendio.com>
9 *
10 *
11 * Loosely based on the mixer applet:
12 *
13 * GNOME audio mixer module
14 * (C) 1998 The Free Software Foundation
15 *
16 * Author: Michael Fulbright <msf@redhat.com>:
17 *
18 * Based on:
19 *
20 * GNOME time/date display module.
21 * (C) 1997 The Free Software Foundation
22 *
23 * Authors: Miguel de Icaza
24 *          Federico Mena
25 *
26 */
27
28/*
29 * Contact Dave Larson <davlarso@acm.org> for questions of
30 * bugs concerning the Solaris audio api code.
31 *
32 */     
33
34#include <config.h>
35
36#include <math.h>
37#include <stdio.h>
38#include <sys/stat.h>
39#include <sys/ioctl.h>
40#include <unistd.h>
41#include <fcntl.h>
42#include <string.h>
43#include <errno.h>
44#include <stdlib.h>
45
46#include <gtk/gtk.h>
47#include <gdk/gdkkeysyms.h>
48#include <libgnome/libgnome.h>
49#include <libgnomeui/gnome-help.h>
50#include <libgnomeui/gnome-about.h>
51#include <libgnomeui/gnome-window-icon.h>
52#include <gconf/gconf-client.h>
53#include <panel-applet-gconf.h>
54
55#ifdef HAVE_LINUX_SOUNDCARD_H
56#include <linux/soundcard.h>
57#define OSS_API
58#elif defined HAVE_MACHINE_SOUNDCARD_H
59#include <machine/soundcard.h>
60#define OSS_API
61#elif defined HAVE_SYS_SOUNDCARD_H
62#include <sys/soundcard.h>
63#define OSS_API
64#elif defined HAVE_SOUNDCARD_H
65#include <soundcard.h>
66#define OSS_API
67#elif defined HAVE_SYS_AUDIOIO_H
68#include <sys/audioio.h>
69#define SUN_API
70#elif defined HAVE_SYS_AUDIO_IO_H
71#include <sys/audio.io.h>
72#define SUN_API
73#elif defined HAVE_SUN_AUDIOIO_H
74#include <sun/audioio.h>
75#define SUN_API
76#elif defined HAVE_DMEDIA_AUDIO_H
77#define IRIX_API
78#include <dmedia/audio.h>
79#elif defined HAVE_SYS_AUDIO_H
80#include <sys/audio.h>
81#define AIX_API
82#elif defined HAVE_GSTREAMER
83#define GSTREAMER_API
84#include <gst/gst.h>
85#include <gst/mixer/mixer.h>
86#include <gst/propertyprobe/propertyprobe.h>
87#else
88#error No soundcard defenition!
89#endif /* SOUNDCARD_H */
90
91#ifdef OSS_API
92#define VOLUME_MAX 100
93#define ALLOW_PREFERENCES
94#endif
95#ifdef SUN_API
96#define VOLUME_MAX 255
97#endif
98#ifdef IRIX_API
99#define VOLUME_MAX 255
100#endif
101#ifdef AIX_API
102#define VOLUME_MAX 160
103#endif
104#ifdef GSTREAMER_API
105#define VOLUME_MAX 100
106#define ALLOW_PREFERENCES
107#endif
108
109#define VOLUME_STOCK_MUTE             "volume-mute"
110#define VOLUME_STOCK_ZERO             "volume-zero"
111#define VOLUME_STOCK_MIN              "volume-min"
112#define VOLUME_STOCK_MED              "volume-med"
113#define VOLUME_STOCK_MAX              "volume-max"
114
115#define VOLUME_DEFAULT_ICON_SIZE 48
116static GtkIconSize volume_icon_size = 0;
117static gboolean icons_initialized = FALSE;
118static gboolean device_present = TRUE;
119
120#define IS_PANEL_HORIZONTAL(o) (o == PANEL_APPLET_ORIENT_UP || o == PANEL_APPLET_ORIENT_DOWN)
121
122#ifdef OSS_API
123
124typedef struct {
125        gint channel;
126        gchar *name;
127} ChannelData;
128
129static gchar *channel_names [] = {
130        N_("Main Volume"), N_("Bass"), N_("Treble"), N_("Synth"),
131        N_("Pcm"), N_("Speaker"), N_("Line"),
132        N_("Microphone"), N_("CD"), N_("Mix"), N_("Pcm2"),
133        N_("Recording Level"), N_("Input Gain"), N_("Output Gain"),
134        N_("Line1"), N_("Line2"), N_("Line3"), N_("Digital1"),
135        N_("Digital2"), N_("Digital3"),
136        N_("Phone Input"), N_("Phone Output"), N_("Video"), N_("Radio"), N_("Monitor")
137};
138#endif
139
140
141#ifdef GSTREAMER_API
142typedef struct {
143        gint           channel;
144        const gchar   *name;
145        GstMixer      *mixer;
146        GstMixerTrack *track;
147} ChannelData;
148#endif
149
150typedef struct {
151        PanelAppletOrient  orientation;
152
153        gint               timeout;
154        gboolean       mute;
155        gint               vol;
156        gint               vol_before_popup;
157        gint                channel;
158        int                 mixerchannel;
159        GList               *channels;
160        gchar               *device;
161        gint                panel_size;
162
163        GtkAdjustment     *adj;
164
165        GtkWidget         *about_dialog;
166
167        GtkWidget         *applet;
168        GtkWidget         *frame;
169        GtkWidget         *image;
170        GtkWidget               *prefdialog;
171       
172        /* The popup window and scale. */
173        GtkWidget         *popup;
174        GtkWidget         *scale;
175       
176        GtkWidget               *error_dialog;
177
178        GdkPixbuf         *zero;
179        GdkPixbuf         *min;
180        GdkPixbuf         *max;
181        GdkPixbuf         *med;
182        GdkPixbuf         *muted;
183
184        GtkTooltips       *tooltips;
185} MixerData;
186
187static void mixer_update_slider (MixerData *data);
188static void mixer_update_image  (MixerData *data);
189static void mixer_popup_show    (MixerData *data);
190static void mixer_popup_hide    (MixerData *data, gboolean revert);
191
192static void mixer_start_gmix_cb (BonoboUIComponent *uic,
193                                 MixerData         *data,
194                                 const gchar       *verbname);
195static void mixer_ui_component_event (BonoboUIComponent *comp,
196                          const gchar                  *path,
197                          Bonobo_UIComponent_EventType  type,
198                          const gchar                  *state_string,
199                          MixerData                    *data);
200void add_atk_namedesc (GtkWidget *widget, const gchar *name, const gchar *desc);
201
202#ifdef AIX_API
203static gint mstvolfd = -1; /* Used to change the master volume level */
204#define MAX_MPX_CHAN_LEN 20
205#define DEFAULT_MST_VOLUME 100
206#endif
207static gint mixerfd = -1;
208static gchar *run_mixer_cmd = NULL;
209
210static const gchar *access_name = N_("Volume Control");     
211static const gchar *access_name_mute = N_("Volume Control (muted)");
212static const gchar *access_name_nodevice = N_("No audio device");
213gboolean gail_loaded = FALSE; 
214
215
216/*
217 * GStreamer-specific code
218 */
219
220#ifdef GSTREAMER_API
221
222static ChannelData *
223gstreamer_find_mixer_channel (MixerData *data)
224{
225        const GList *iter;
226        ChannelData *cdata;
227
228        for (iter = data->channels; iter != NULL; iter = iter->next) {
229                cdata = iter->data;
230                if (cdata->channel == data->mixerchannel)
231                        return cdata;
232        }
233       
234        return NULL;
235}
236
237/*
238  Map a volume value from the track's [min_volume, max_volume]
239   to [0, VOLUME_MAX].
240*/
241static gint
242gstreamer_normalize_volume (GstMixerTrack *track,
243                            int            vol)
244{
245        double t;
246
247        vol = CLAMP (vol, track->min_volume, track->max_volume);
248
249        t = (vol - track->min_volume) /
250                (double) (track->max_volume - track->min_volume);
251
252        return (gint) (VOLUME_MAX * t + 0.5);
253}
254
255/*
256  Map a volume value from [0, VOLUME_MAX] to the track's
257  [min_volume, max_volume].
258*/
259static gint
260gstreamer_trackify_volume (GstMixerTrack *track,
261                           int            vol)
262{
263        double t;
264
265        vol = CLAMP (vol, 0, VOLUME_MAX);
266
267        t = vol / (double) VOLUME_MAX;
268       
269        return (gint) (t * (track->max_volume - track->min_volume) + 0.5
270                       + track->min_volume);
271}
272
273static int
274gstreamer_get_volume (MixerData *data)
275{
276        ChannelData *cdata;
277        gint *volumes;
278        gint i;
279        double vol = 0;
280       
281        cdata = gstreamer_find_mixer_channel (data);
282        if (cdata == NULL)
283                return 0; /* This is sort of lame */
284
285        volumes = g_new0 (gint, cdata->track->num_channels);
286        gst_mixer_get_volume (cdata->mixer, cdata->track, volumes);
287        for (i = 0; i < cdata->track->num_channels; ++i) {
288                vol += volumes[i];
289        }
290        g_free (volumes);
291       
292        vol /= cdata->track->num_channels;
293
294        return gstreamer_normalize_volume (cdata->track, (gint) vol);
295}
296
297static void
298gstreamer_set_volume (MixerData *data, gint vol)
299{
300        ChannelData *cdata;
301        gint *volumes;
302        gint i;
303
304        cdata = gstreamer_find_mixer_channel (data);
305        if (cdata == NULL)
306                return; /* This is also sort of lame */
307
308        vol = gstreamer_trackify_volume (cdata->track, vol);
309       
310        volumes = g_new0 (gint, cdata->track->num_channels);
311        for (i = 0; i < cdata->track->num_channels; ++i) {
312                volumes[i] = vol;
313        }
314        gst_mixer_set_volume (cdata->mixer, cdata->track, volumes);
315        g_free (volumes);
316}
317
318/*
319  This code is stolen from gnome-media's gst-mixer
320*/
321static void
322gstreamer_discover_mixers (MixerData *data)
323{
324        const GList *elements;
325        gint num = 0, channel_count = 0;
326       
327        /* go through all elements of a certain class and check whether
328         * they implement a mixer. If so, add a page */
329        elements = gst_registry_pool_feature_list (GST_TYPE_ELEMENT_FACTORY);
330        for ( ; elements != NULL; elements = elements->next) {
331                GstElementFactory *factory = GST_ELEMENT_FACTORY (elements->data);
332                gchar *title = NULL;
333                const gchar *klass;
334                GstElement *element = NULL;
335                const GParamSpec *devspec;
336                GstPropertyProbe *probe;
337                GValueArray *array = NULL;
338                gint n;
339                const GList *tracks;
340               
341                /* check category */
342                klass = gst_element_factory_get_klass (factory);
343                if (strcmp (klass, "Generic/Audio"))
344                        goto next;
345               
346                /* create element */
347                title = g_strdup_printf ("gst-mixer-%d", num);
348                element = gst_element_factory_create (factory, title);
349                if (!element)
350                        goto next;
351               
352                if (!GST_IS_PROPERTY_PROBE (element))
353                        goto next;
354               
355                probe = GST_PROPERTY_PROBE (element);
356                if (!(devspec = gst_property_probe_get_property (probe, "device")))
357                        goto next;
358                if (!(array = gst_property_probe_probe_and_get_values (probe, devspec)))   
359                        goto next;                                                               
360               
361                /* set all devices and test for mixer */
362                for (n = 0; n < array->n_values; n++) {
363                        GValue *device = g_value_array_get_nth (array, n);
364                       
365                        /* set this device */
366                        g_object_set_property (G_OBJECT (element), "device", device);
367                        if (gst_element_set_state (element,
368                                                   GST_STATE_READY) == GST_STATE_FAILURE)
369                                continue;
370                       
371                        /* Is this device a mixer?  If so, add it to the list. */
372                        if (!GST_IS_MIXER (element)) {
373                                gst_element_set_state (element, GST_STATE_NULL);
374                                continue;
375                        }
376                       
377                        tracks = gst_mixer_list_tracks (GST_MIXER (element));
378                        for (; tracks != NULL; tracks = tracks->next) {
379                                ChannelData *cdata;
380                                GstMixerTrack *track = tracks->data;
381                               
382                                cdata = g_new0 (ChannelData, 1);
383                                cdata->channel = channel_count;
384                                cdata->name = track->label;
385                                cdata->mixer = GST_MIXER (element);
386                                cdata->track = track;
387                               
388                                g_object_ref (cdata->mixer);
389                                g_object_ref (cdata->track);
390                               
391                                data->channels = g_list_append (data->channels, cdata);
392                                channel_count++;
393                        }
394                       
395                        num++;
396                       
397                        /* and recreate this object, since we give it to the mixer */
398                        title = g_strdup_printf ("gst-mixer-%d", num);
399                        element = gst_element_factory_create (factory, title);
400                }
401               
402        next:
403                if (element)
404                        gst_object_unref (GST_OBJECT (element));
405                if (array)
406                        g_value_array_free (array);
407                g_free (title);
408        }
409}
410
411static void
412gstreamer_channel_data_free (ChannelData *cdata)
413{
414        g_object_unref (cdata->mixer);
415        g_object_unref (cdata->track);
416        g_free (cdata);
417}
418
419static gboolean
420gstreamer_init (MixerData *data)
421{
422        if (! gst_init_check (NULL, NULL))
423                return FALSE;
424        gstreamer_discover_mixers (data);
425        return TRUE;
426}
427
428#endif
429
430
431#ifdef IRIX_API
432/*
433 * Note: we are using the obsolete API to increase portability.
434 * /usr/sbin/audiopanel provides many more options...
435 */
436#define MAX_PV_BUF      4
437long pv_buf[MAX_PV_BUF] = {
438  AL_LEFT_SPEAKER_GAIN, 0L, AL_RIGHT_SPEAKER_GAIN, 0L
439};
440#endif
441
442static gboolean
443key_writable (PanelApplet *applet, const char *key)
444{
445        gboolean writable;
446        char *fullkey;
447        static GConfClient *client = NULL;
448        if (client == NULL)
449                client = gconf_client_get_default ();
450
451        fullkey = panel_applet_gconf_get_full_key (applet, key);
452
453        writable = gconf_client_key_is_writable (client, fullkey, NULL);
454
455        g_free (fullkey);
456
457        return writable;
458}
459
460#ifdef OSS_API
461static void
462get_channels (MixerData *data)
463{
464        gint res, i;
465        int devmask;
466        gboolean valid = FALSE;
467       
468        res = ioctl(mixerfd, MIXER_READ(SOUND_MIXER_DEVMASK), &devmask);
469        if (res != 0) {
470                return;
471        }
472        for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
473                ChannelData *cdata;
474                if (devmask & (1 << i)) {
475                        cdata = g_new0 (ChannelData, 1);
476                        cdata->channel = i;
477                        cdata->name = channel_names[i];
478                        data->channels = g_list_append (data->channels, cdata);
479                        if (data->mixerchannel == i)
480                                valid = TRUE;
481                }
482        }
483        /* selected channel does not work. Set it to something that does */
484        if (!valid) {
485                ChannelData *cdata = data->channels->data;
486                data->mixerchannel = cdata->channel;
487                if (key_writable (PANEL_APPLET (data->applet), "channel"))
488                        panel_applet_gconf_set_int (PANEL_APPLET (data->applet), "channel",
489                                                    data->mixerchannel, NULL);
490        }
491}
492#endif
493
494
495/********************** Mixer related Code *******************/
496/*  Mostly based on the gmix code                            */
497/*************************************************************/
498static gboolean
499openMixer(const gchar *device_name, MixerData *data)
500{
501        gint res, ver;
502#ifdef OSS_API
503       
504
505        mixerfd = open(device_name, O_RDWR, 0);
506#endif
507#ifdef SUN_API
508        mixerfd = open(device_name, O_RDWR);
509#endif
510#ifdef IRIX_API
511        /*
512         * This is a result code, not a file descriptor, and we ignore
513         * the values read.  But the call is useful to see if we can
514         * access the default output port.
515         */
516        mixerfd = ALgetparams(AL_DEFAULT_DEVICE, pv_buf, MAX_PV_BUF);
517#endif
518#ifdef AIX_API
519        gchar iomixchannel[MAX_MPX_CHAN_LEN];
520        gchar mstvolchannel[MAX_MPX_CHAN_LEN];
521       
522        /* Construct the string to use the iomix multiplex channel
523         * of the device */
524        sprintf(iomixchannel, "%s%s", device_name, "/iomix");
525        sprintf(mstvolchannel, "%s%s", device_name, "/mstvol");
526
527        mstvolfd = open (mstvolchannel, O_WRONLY);
528        if (mstvolfd < 0) {
529                /* probably should die more gracefully */               
530                return FALSE;
531        }
532
533        mixerfd = open(iomixchannel, O_WRONLY);
534#endif
535        if (mixerfd < 0) {
536                /* probably should die more gracefully */               
537                return FALSE;
538        }
539
540        /*
541         * check driver-version
542         */
543#ifdef OSS_GETVERSION
544        res=ioctl(mixerfd, OSS_GETVERSION, &ver);
545        if ((res==0) && (ver!=SOUND_VERSION)) {
546                g_message(_("warning: this version of gmix was compiled "
547                        "with a different version of\nsoundcard.h.\n"));
548        }
549#endif
550#ifdef OSS_API
551        /*
552         * Check whether this mixer actually supports the channel
553         * we're going to try to monitor.
554         */
555         get_channels (data);
556
557#endif 
558        return TRUE;   
559}
560
561/* only works with master vol currently */
562static int
563readMixer(MixerData *data)
564{
565#ifdef GSTREAMER_API
566  return gstreamer_get_volume (data);
567#endif
568#ifdef OSS_API
569        gint vol, r, l;
570        /* if we couldn't open the mixer */
571        if (mixerfd < 0) return 0;
572
573        ioctl(mixerfd, MIXER_READ(data->mixerchannel), &vol);
574
575        l = vol & 0xff;
576        r = (vol & 0xff00) >> 8;
577/*      printf("vol=%d l=%d r=%d\n",vol, l, r); */
578
579        return (r+l)/2;
580#endif
581#ifdef SUN_API
582        audio_info_t ainfo;
583        AUDIO_INITINFO (&ainfo);       
584        ioctl (mixerfd, AUDIO_GETINFO, &ainfo);
585        return (ainfo.play.gain);
586#endif
587#ifdef IRIX_API
588        /*
589         * Average the current gain settings.  If we can't read the
590         * current levels use the values from the previous read.
591         */
592        (void) ALgetparams(AL_DEFAULT_DEVICE, pv_buf, MAX_PV_BUF);
593        return (pv_buf[1] + pv_buf[3]) / 2;
594#endif
595#ifdef AIX_API
596        gint rc;
597        audio_channel_status info_channels;
598       
599        info_channels.channel_count = 0;
600        rc = ioctl (mixerfd, AUDIO_CHANNEL_STATUS, &info_channels);
601        if (rc < 0) return DEFAULT_MST_VOLUME;
602        else return info_channels.master_volume;
603#endif /* AIX_API */
604}
605
606static void
607setMixer(gint vol, MixerData *data)
608{
609        gint tvol;
610#ifdef GSTREAMER_API
611        gstreamer_set_volume (data, vol);
612#endif
613#ifdef OSS_API
614        /* if we couldn't open the mixer */
615        if (mixerfd < 0) return;
616
617        tvol = (vol << 8) + vol;
618/*g_message("Saving mixer value of %d",tvol);*/
619        ioctl(mixerfd, MIXER_WRITE(data->mixerchannel), &tvol);
620       
621/* SOUND_MIXER_SPEAKER is output level on Mac, but input level on PC. #96639 */
622#ifdef __powerpc__
623        ioctl(mixerfd, MIXER_WRITE(SOUND_MIXER_SPEAKER), &tvol);
624#endif
625#endif
626#ifdef SUN_API
627        audio_info_t ainfo;
628        AUDIO_INITINFO (&ainfo);
629        ainfo.play.gain = vol;
630        ioctl (mixerfd, AUDIO_SETINFO, &ainfo);
631#endif
632#ifdef IRIX_API
633        if (vol < 0)
634          tvol = 0;
635        else if (vol > VOLUME_MAX)
636          tvol = VOLUME_MAX;
637        else
638          tvol = vol;
639
640        pv_buf[1] = pv_buf[3] = tvol;
641        (void) ALsetparams(AL_DEFAULT_DEVICE, pv_buf, MAX_PV_BUF);
642#endif
643#ifdef AIX_API
644        int master_volume;
645
646        master_volume = (int)CLAMP (vol, 0, VOLUME_MAX);
647        ioctl (mstvolfd, AUDIO_MASTER_VOLUME, &master_volume);
648#endif
649}
650
651static void
652mixer_value_changed_cb (GtkAdjustment *adj, MixerData *data)
653{
654        gint vol;
655
656        vol = adj->value;
657       
658        data->vol = vol;
659
660        mixer_update_image (data);
661
662        if (!data->mute) {
663                setMixer (vol, data);
664        }
665}
666
667#ifdef SUN_API
668static gboolean
669set_mute_status (gboolean mute_state)
670{
671        audio_info_t ainfo;
672        AUDIO_INITINFO (&ainfo);
673        ainfo.output_muted = mute_state;
674        ioctl (mixerfd, AUDIO_SETINFO, &ainfo);
675}
676
677static gboolean
678get_mute_status ()
679{
680        audio_info_t ainfo;
681        AUDIO_INITINFO (&ainfo);
682        ioctl (mixerfd, AUDIO_GETINFO, &ainfo);
683        return (ainfo.output_muted);
684}
685#endif
686
687static gboolean
688mixer_timeout_cb (MixerData *data)
689{
690        BonoboUIComponent *component;
691        gint               vol;
692        gboolean state;
693       
694        if (!device_present) {
695                data->mute = TRUE;
696                mixer_update_image (data);
697
698                gtk_tooltips_set_tip (data->tooltips,
699                                      data->applet,
700                                      _(access_name_nodevice),
701                                      NULL);
702                if (gail_loaded) {
703                        add_atk_namedesc (data->applet,
704                                          _(access_name_nodevice),
705                                          NULL);
706                }
707                return TRUE;
708        }
709
710#ifdef SUN_API
711        state = get_mute_status ();
712
713        if (state != data->mute) {
714                data->mute = state;
715                if (data->mute) {
716                        mixer_update_image (data);
717
718
719                        gtk_tooltips_set_tip (data->tooltips,
720                                              data->applet,
721                                              _(access_name_mute),
722                                              NULL);
723                        if (gail_loaded) {
724                                add_atk_namedesc (data->applet,
725                                                  _(access_name_mute),
726                                                  NULL);
727                        }
728                        component = panel_applet_get_popup_component (PANEL_APPLET (data->applet));
729                        bonobo_ui_component_set_prop (component,
730                                                      "/commands/Mute",
731                                                      "state",
732                                                      "1",
733                                                      NULL);
734
735                } else {
736                        mixer_update_image (data);
737
738                        gtk_tooltips_set_tip (data->tooltips,
739                                              data->applet,
740                                              _(access_name),
741                                              NULL);
742                        if (gail_loaded) {
743                                add_atk_namedesc (data->applet,
744                                                  _(access_name),
745                                                  NULL);
746                        }
747                        component = panel_applet_get_popup_component (PANEL_APPLET (data->applet));
748                        bonobo_ui_component_set_prop (component,
749                                                      "/commands/Mute",
750                                                      "state",
751                                                      "0",
752                                                      NULL);
753
754                }
755        }
756#endif
757        vol = readMixer (data);
758
759#ifndef SUN_API
760        /* Some external program changed the volume, get out of mute mode. */
761        if (data->mute && (vol > 0)) {
762                data->mute = FALSE;
763
764                /* Sync the menu. */
765                component = panel_applet_get_popup_component (PANEL_APPLET (data->applet));
766
767                bonobo_ui_component_set_prop (component,
768                                              "/commands/Mute",
769                                              "state",
770                                              "0",
771                                              NULL);
772               
773                gtk_tooltips_set_tip (data->tooltips,
774                                      data->applet,
775                                      _(access_name),
776                                      NULL);
777                if (gail_loaded) {
778                        add_atk_namedesc (data->applet, _(access_name), NULL);
779                }
780        }
781#endif
782
783        if (!data->mute && vol != data->vol) {
784                data->vol = vol;
785                mixer_update_slider (data);
786        }
787       
788       
789        return TRUE;
790}
791
792static void
793mixer_update_slider (MixerData *data)
794{
795        gint vol = data->vol;
796
797        gtk_adjustment_set_value (data->adj, vol);
798}
799
800static void
801mixer_load_volume_images (MixerData *data) {
802        if (data->zero)
803                g_object_unref (data->zero);
804        if (data->min)
805                g_object_unref (data->min);
806        if (data->med)
807                g_object_unref (data->med);
808        if (data->max)
809                g_object_unref (data->max);
810        if (data->muted)
811                g_object_unref (data->muted);
812
813        data->zero = gtk_widget_render_icon (data->applet, VOLUME_STOCK_ZERO,
814                                             volume_icon_size, NULL);
815        data->min = gtk_widget_render_icon (data->applet, VOLUME_STOCK_MIN,
816                                             volume_icon_size, NULL);
817        data->med = gtk_widget_render_icon (data->applet, VOLUME_STOCK_MED,
818                                             volume_icon_size, NULL);
819        data->max = gtk_widget_render_icon (data->applet, VOLUME_STOCK_MAX,
820                                             volume_icon_size, NULL);
821        data->muted = gtk_widget_render_icon (data->applet, VOLUME_STOCK_MUTE,
822                                             volume_icon_size, NULL);
823}
824
825static void
826applet_size_allocate (GtkWidget *widget, GtkAllocation *allocation, MixerData *data)
827{
828        if (IS_PANEL_HORIZONTAL (data->orientation)) {
829                if (data->panel_size != allocation->height) {
830                        data->panel_size = allocation->height;
831                        mixer_update_image (data);
832                }
833        }
834        else {
835                if (data->panel_size != allocation->width) {
836                        data->panel_size = allocation->width;
837                        mixer_update_image (data);
838                }
839        }
840                       
841}
842
843
844static void
845mixer_update_image (MixerData *data)
846{
847        gint vol, size;
848        GdkPixbuf *pixbuf, *copy, *scaled = NULL;
849        vol = data->vol;
850       
851        if (vol <= 0)
852                pixbuf = gdk_pixbuf_copy (data->zero);
853        else if (vol <= VOLUME_MAX / 3)
854                pixbuf = gdk_pixbuf_copy (data->min);
855        else if (vol <= 2 * VOLUME_MAX / 3)
856                pixbuf = gdk_pixbuf_copy (data->med);
857        else
858                pixbuf = gdk_pixbuf_copy (data->max);
859
860        if (data->mute) {
861                GdkPixbuf *mute = gdk_pixbuf_copy (data->muted);
862
863                gdk_pixbuf_composite (mute,
864                                      pixbuf,
865                                      0,
866                                      0,
867                                      gdk_pixbuf_get_width (mute),
868                                      gdk_pixbuf_get_height (mute),
869                                      0,
870                                      0,
871                                      1.0,
872                                      1.0,
873                                      GDK_INTERP_BILINEAR,
874                                      255);
875                g_object_unref (mute);
876        }
877
878        size = MAX (11, data->panel_size);
879        scaled = gdk_pixbuf_scale_simple (pixbuf, size, size, GDK_INTERP_BILINEAR);
880        gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), scaled);
881        g_object_unref (scaled);       
882        g_object_unref (pixbuf);
883       
884        if (key_writable (PANEL_APPLET (data->applet), "vol"))
885                panel_applet_gconf_set_int (PANEL_APPLET (data->applet), "vol", data->vol,
886                                            NULL);
887        if (key_writable (PANEL_APPLET (data->applet), "mute"))
888                panel_applet_gconf_set_bool (PANEL_APPLET (data->applet), "mute", data->mute,
889                                             NULL);
890                       
891}
892
893static gboolean
894scale_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, MixerData *data)
895{
896        switch (event->keyval) {
897        case GDK_Escape:
898                /* Revert. */
899                mixer_popup_hide (data, TRUE);
900                return TRUE;
901       
902        case GDK_KP_Enter:
903        case GDK_ISO_Enter:
904        case GDK_3270_Enter:
905        case GDK_Return:
906        case GDK_space:
907        case GDK_KP_Space:
908                /* Apply. */
909                mixer_popup_hide (data, FALSE);
910                return TRUE;
911
912        default:
913                break;
914        }
915
916        return FALSE;
917}
918
919static void
920error_response (GtkDialog *dialog, gint id, MixerData *data)
921{
922        gtk_widget_destroy (data->error_dialog);
923        data->error_dialog = NULL;
924}
925
926static void
927show_error_dialog (MixerData *data)
928{       
929        if (data->error_dialog) {
930                gtk_window_present (GTK_WINDOW (data->error_dialog));
931                return;
932        }
933        data->error_dialog = gtk_message_dialog_new (NULL,
934                                         GTK_DIALOG_DESTROY_WITH_PARENT,
935                                         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
936                                         _("Couldn't open mixer device %s\n"),
937                                         data->device, NULL);
938
939        gtk_window_set_screen (GTK_WINDOW (data->error_dialog),
940                               gtk_widget_get_screen (GTK_WIDGET (data->applet)));
941        g_signal_connect (GTK_OBJECT (data->error_dialog),
942                             "response",
943                             G_CALLBACK (error_response),
944                             data);
945        gtk_widget_show_all (data->error_dialog);
946}
947
948static gboolean
949scale_button_release_event_cb (GtkWidget *widget, GdkEventButton *event, MixerData *data)
950{
951
952        if (data->popup != NULL) {
953                mixer_popup_hide (data, FALSE);
954                return TRUE;
955        }
956
957        return FALSE;
958}
959
960static gboolean
961scale_button_event  (GtkWidget *widget, GdkEventButton *event, MixerData *data)
962{
963        return TRUE;
964}
965
966static gboolean
967applet_button_release_event_cb (GtkWidget *widget, GdkEventButton *event, MixerData *data)
968{
969
970        if (event->button == 1) {
971                if (data->popup == NULL && device_present) {
972                        mixer_popup_show (data);
973                        return TRUE;
974                } else if (!device_present)
975                        show_error_dialog (data);
976        }
977
978        return FALSE;
979}
980
981static gboolean
982applet_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, MixerData *data)
983{
984        switch (event->keyval) {
985        case GDK_Escape:
986                /* Revert. */
987                mixer_popup_hide (data, TRUE);
988                return TRUE;
989        case GDK_m:
990                if (event->state == GDK_CONTROL_MASK) {
991                        Bonobo_UIComponent_EventType  type;
992                        if (data->mute)
993                                mixer_ui_component_event (NULL, "Mute", type, "0", data);
994                        else
995                                mixer_ui_component_event (NULL, "Mute", type, "1", data);
996                        return TRUE;
997                }
998                break;
999        case GDK_o:
1000                if (event->state == GDK_CONTROL_MASK) {
1001                        mixer_start_gmix_cb (NULL, data, NULL);
1002                        return TRUE;
1003                }
1004                break;
1005        case GDK_KP_Enter:
1006        case GDK_ISO_Enter:
1007        case GDK_3270_Enter:
1008        case GDK_Return:
1009        case GDK_space:
1010        case GDK_KP_Space:
1011                /* Apply. */
1012                if (data->popup != NULL)
1013                        mixer_popup_hide (data, FALSE);
1014                else if(device_present)
1015                        mixer_popup_show (data);
1016                else
1017                        show_error_dialog (data);
1018                return TRUE;
1019
1020        default:
1021                break;
1022        }
1023
1024        return FALSE;
1025}
1026
1027
1028static gboolean
1029applet_scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, MixerData *data)
1030{
1031        gint direction;
1032        if (event->type != GDK_SCROLL ) {
1033                return FALSE;
1034        }
1035
1036        direction = event->direction;
1037
1038        switch(direction) {
1039        case GDK_SCROLL_UP:
1040                data->vol += 5;
1041                break;
1042        case GDK_SCROLL_DOWN:
1043                data->vol -= 5;
1044                break;
1045        default:
1046                break;
1047        }
1048
1049        if (data->vol > VOLUME_MAX) data->vol = VOLUME_MAX;
1050        if (data->vol < 0) data->vol = 0;
1051
1052        mixer_update_slider(data);
1053
1054        return TRUE;
1055}
1056
1057static void
1058mixer_popup_show (MixerData *data)
1059{
1060        GtkRequisition  req;
1061        GtkWidget      *frame;
1062        GtkWidget      *inner_frame;
1063        GtkWidget      *pluslabel, *minuslabel;
1064        GtkWidget            *event;
1065        GtkWidget            *box;
1066        gint            x, y;
1067        gint            width, height;
1068        GdkGrabStatus   pointer, keyboard;
1069
1070        data->popup = gtk_window_new (GTK_WINDOW_POPUP);
1071        gtk_window_set_screen (GTK_WINDOW (data->popup),
1072                               gtk_widget_get_screen (data->applet));
1073
1074        data->vol_before_popup = readMixer (data);
1075       
1076        frame = gtk_frame_new (NULL);
1077        gtk_container_set_border_width (GTK_CONTAINER (frame), 0);
1078        gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
1079        gtk_widget_show (frame);
1080       
1081        inner_frame = gtk_frame_new (NULL);
1082        gtk_container_set_border_width (GTK_CONTAINER (inner_frame), 0);
1083        gtk_frame_set_shadow_type (GTK_FRAME (inner_frame), GTK_SHADOW_NONE);
1084        gtk_widget_show (inner_frame);
1085       
1086        event = gtk_event_box_new ();
1087        /* This signal is to not let button press close the popup when the press is
1088        ** in the scale */
1089        g_signal_connect_after (event, "button_press_event",
1090                                  G_CALLBACK (scale_button_event), data);
1091       
1092        if (IS_PANEL_HORIZONTAL (data->orientation)) {
1093                box = gtk_vbox_new (FALSE, 0);
1094                data->scale = gtk_vscale_new (data->adj);
1095                gtk_widget_set_size_request (data->scale, -1, 100);
1096                gtk_range_set_inverted (GTK_RANGE (data->scale), TRUE);
1097        } else {
1098                box = gtk_hbox_new (FALSE, 0);
1099                data->scale = gtk_hscale_new (data->adj);
1100                gtk_widget_set_size_request (data->scale, 100, -1);
1101        }
1102       
1103        if (gail_loaded) {
1104                add_atk_namedesc (data->scale,
1105                                _("Volume Controller"),
1106                                _("Use Up/Down arrow keys to change volume"));
1107        }
1108        g_signal_connect_after (data->popup,
1109                          "button-press-event",
1110                          (GCallback) scale_button_release_event_cb,
1111                          data);
1112
1113        g_signal_connect (data->scale,
1114                          "key-press-event",
1115                          (GCallback) scale_key_press_event_cb,
1116                          data);
1117       
1118        gtk_widget_show (data->scale);
1119        gtk_scale_set_draw_value (GTK_SCALE (data->scale), FALSE);
1120       
1121        gtk_range_set_update_policy (GTK_RANGE (data->scale),
1122                                     GTK_UPDATE_CONTINUOUS);
1123       
1124        gtk_container_add (GTK_CONTAINER (data->popup), frame);
1125       
1126        gtk_container_add (GTK_CONTAINER (frame), inner_frame);
1127       
1128        /* Translators - The + and - refer to increasing and decreasing the volume.
1129        ** I don't know if there are sensible alternatives in other languages */
1130        pluslabel = gtk_label_new (_("+"));     
1131        minuslabel = gtk_label_new (_("-"));
1132       
1133        if (IS_PANEL_HORIZONTAL (data->orientation)) {
1134                gtk_box_pack_start (GTK_BOX (box), pluslabel, FALSE, FALSE, 0);
1135                gtk_box_pack_end (GTK_BOX (box), minuslabel, FALSE, FALSE, 0);
1136        } else {
1137                gtk_box_pack_start (GTK_BOX (box), minuslabel, FALSE, FALSE, 0);
1138                gtk_box_pack_end (GTK_BOX (box), pluslabel, FALSE, FALSE, 0);
1139        }
1140        gtk_box_pack_start (GTK_BOX (box), data->scale, TRUE, TRUE, 0);
1141       
1142        gtk_container_add (GTK_CONTAINER (event), box);
1143        gtk_container_add (GTK_CONTAINER (inner_frame), event);
1144       
1145        gtk_widget_show_all (inner_frame);
1146       
1147        /*
1148         * Position the popup right next to the button.
1149         */
1150        gtk_widget_size_request (data->popup, &req);
1151       
1152        gdk_window_get_origin (data->image->window, &x, &y);
1153        gdk_drawable_get_size (data->image->window, &width, &height);
1154       
1155        switch (data->orientation) {
1156        case PANEL_APPLET_ORIENT_DOWN:
1157                x += (width - req.width) / 2;
1158                y += height;
1159                break;
1160               
1161        case PANEL_APPLET_ORIENT_UP:
1162                x += (width - req.width) / 2;
1163                y -= req.height;
1164                break;
1165               
1166        case PANEL_APPLET_ORIENT_LEFT:
1167                x -= req.width;
1168                y += (height - req.height) / 2;                 
1169                break;
1170               
1171        case PANEL_APPLET_ORIENT_RIGHT:
1172                x += width;
1173                y += (height - req.height) / 2;                 
1174                break;
1175               
1176        default:
1177                break;
1178        }
1179       
1180        if (x < 0) {
1181                x = 0;
1182        }
1183        if (y < 0) {
1184                y = 0;
1185        }
1186       
1187        gtk_window_move (GTK_WINDOW (data->popup), x, y);
1188       
1189        gtk_widget_show (data->popup);
1190       
1191        /*
1192         * Grab focus and pointer.
1193         */
1194        gtk_widget_grab_focus (data->popup);
1195        gtk_grab_add (data->popup);
1196
1197        pointer = gdk_pointer_grab (data->popup->window,
1198                                    TRUE,
1199                                    (GDK_BUTTON_PRESS_MASK |
1200                                    GDK_BUTTON_RELEASE_MASK
1201                                     | GDK_POINTER_MOTION_MASK),
1202                                    NULL, NULL, GDK_CURRENT_TIME);
1203       
1204        keyboard = gdk_keyboard_grab (data->popup->window,
1205                                      TRUE,
1206                                      GDK_CURRENT_TIME);
1207       
1208        if (pointer != GDK_GRAB_SUCCESS || keyboard != GDK_GRAB_SUCCESS) {
1209                /* We could not grab. */
1210                gtk_grab_remove (data->popup);
1211                gtk_widget_destroy (data->popup);
1212                data->popup = NULL;
1213                data->scale = NULL;
1214               
1215                if (pointer == GDK_GRAB_SUCCESS) {
1216                        gdk_keyboard_ungrab (GDK_CURRENT_TIME);
1217                }
1218                if (keyboard == GDK_GRAB_SUCCESS) {
1219                        gdk_pointer_ungrab (GDK_CURRENT_TIME);
1220                }
1221               
1222                g_warning ("mixer_applet2: Could not grab X server.");
1223                return;
1224        }
1225
1226        gtk_frame_set_shadow_type (GTK_FRAME (data->frame), GTK_SHADOW_IN);
1227}
1228
1229static void
1230mixer_popup_hide (MixerData *data, gboolean revert)
1231{
1232        gint vol;
1233       
1234        if (data->popup) {
1235                gtk_grab_remove (data->scale);
1236                gdk_pointer_ungrab (GDK_CURRENT_TIME);         
1237                gdk_keyboard_ungrab (GDK_CURRENT_TIME);
1238
1239                /* Ref the adjustment or it will get destroyed
1240                 * when the scale goes away.
1241                 */
1242                g_object_ref (G_OBJECT (data->adj));
1243
1244                gtk_widget_destroy (GTK_WIDGET (data->popup));
1245
1246                data->popup = NULL;
1247                data->scale = NULL;
1248
1249                if (revert) {
1250                        vol = data->vol_before_popup;
1251                        setMixer (vol, data);
1252                } else {
1253                        /* Get a soft beep like sound to play here, like
1254                         * on MacOS X :) */
1255                        /*gnome_sound_play ("blipp.wav");*/
1256                }
1257
1258                gtk_frame_set_shadow_type (GTK_FRAME (data->frame), GTK_SHADOW_NONE);
1259        }
1260}
1261
1262static void
1263destroy_mixer_cb (GtkWidget *widget, MixerData *data)
1264{
1265
1266#ifdef GSTREAMER_API
1267        GList *iter;
1268        for (iter = data->channels; iter != NULL; iter = iter->next) {
1269                ChannelData *cdata = iter->data;
1270                gstreamer_channel_data_free (cdata);
1271        }
1272#endif
1273
1274#ifdef OSS_API
1275        GList *iter;
1276        for (iter = data->channels; iter != NULL; iter = iter->next) {
1277                ChannelData *cdata = iter->data;
1278                g_free (cdata);
1279        }
1280#endif
1281
1282        if (data->channels != NULL)
1283                g_list_free (data->channels);
1284
1285        if (data->timeout != 0) {
1286                g_source_remove (data->timeout);
1287                data->timeout = 0;
1288        }
1289        if (data->about_dialog)
1290                gtk_widget_destroy (data->about_dialog);
1291        if (data->error_dialog)
1292                gtk_widget_destroy (data->error_dialog);
1293        if (data->tooltips)
1294                g_object_unref (data->tooltips);
1295        if (data->device)
1296                g_free (data->device);
1297        g_free (data);
1298}
1299
1300static void
1301applet_change_background_cb (PanelApplet                *applet,
1302                             PanelAppletBackgroundType  type,
1303                             GdkColor                   *color,
1304                             GdkPixmap                  *pixmap,
1305                             MixerData                  *data)
1306{
1307        if (type == PANEL_NO_BACKGROUND) {
1308                GtkRcStyle *rc_style = gtk_rc_style_new ();
1309
1310                gtk_widget_modify_style (data->applet, rc_style);
1311                gtk_rc_style_unref (rc_style);
1312        }
1313        else if (type == PANEL_COLOR_BACKGROUND) {
1314                gtk_widget_modify_bg (data->applet,
1315                                      GTK_STATE_NORMAL,
1316                                      color);
1317        } else { /* pixmap */
1318                /* FIXME: Handle this when the panel support works again */
1319        }
1320}
1321
1322static void
1323applet_change_orient_cb (GtkWidget *w, PanelAppletOrient o, MixerData *data)
1324{
1325        mixer_popup_hide (data, FALSE);
1326
1327        data->orientation = o;
1328}
1329
1330static void
1331applet_change_size_cb (GtkWidget *w, gint size, MixerData *data)
1332{       
1333        mixer_popup_hide (data, FALSE);
1334        mixer_update_image (data);
1335}
1336
1337static void
1338mixer_start_gmix_cb (BonoboUIComponent *uic,
1339                     MixerData         *data,
1340                     const gchar       *verbname)
1341{
1342        GError *error = NULL;
1343
1344        if (!run_mixer_cmd)
1345                return;
1346
1347        gdk_spawn_command_line_on_screen (
1348                        gtk_widget_get_screen (GTK_WIDGET (data->applet)),
1349                        run_mixer_cmd,
1350                        &error);
1351
1352        if (error) {
1353                GtkWidget *dialog;
1354
1355                dialog = gtk_message_dialog_new (NULL,
1356                                                 GTK_DIALOG_DESTROY_WITH_PARENT,
1357                                                 GTK_MESSAGE_ERROR,
1358                                                 GTK_BUTTONS_OK,
1359                                                 _("There was an error executing '%s': %s"),
1360                                                 run_mixer_cmd,
1361                                                 error->message);
1362
1363                g_signal_connect (dialog, "response",
1364                                  G_CALLBACK (gtk_widget_destroy),
1365                                  NULL);
1366
1367                gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
1368                gtk_window_set_screen (GTK_WINDOW (dialog),
1369                                       gtk_widget_get_screen (data->applet));
1370                gtk_widget_show (dialog);
1371
1372                g_error_free (error);
1373        }
1374}
1375
1376static void
1377mixer_about_cb (BonoboUIComponent *uic,
1378                MixerData         *data,
1379                const gchar       *verbname)
1380{
1381        GdkPixbuf *pixbuf             = NULL;
1382        gchar *file;
1383        static const gchar *authors[] = {
1384                "Richard Hult <richard@imendio.com>",
1385                NULL
1386        };
1387
1388        const gchar *documenters[] = {
1389                NULL
1390        };
1391
1392        const gchar *translator_credits = _("translator_credits");
1393       
1394        if (data->about_dialog) {
1395                gtk_window_set_screen (GTK_WINDOW (data->about_dialog),
1396                                       gtk_widget_get_screen (data->applet));
1397
1398                gtk_window_present (GTK_WINDOW (data->about_dialog));
1399                return;
1400        }
1401       
1402        file = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_PIXMAP, "gnome-mixer-applet.png", TRUE, NULL);
1403        pixbuf = gdk_pixbuf_new_from_file (file, NULL);
1404        g_free (file);
1405
1406        data->about_dialog = gnome_about_new (_("Volume Control"), VERSION,
1407                                              _("(C) 2001 Richard Hult"),
1408                                              _("The volume control lets you set the volume level for your desktop."),
1409                                              authors,
1410                                              documenters,
1411                                              strcmp (translator_credits, "translator_credits") != 0 ? translator_credits : NULL,
1412                                              pixbuf);
1413
1414        g_object_unref (pixbuf);
1415       
1416        gtk_window_set_screen (GTK_WINDOW (data->about_dialog),
1417                               gtk_widget_get_screen (data->applet));
1418
1419        gtk_window_set_wmclass (GTK_WINDOW(data->about_dialog), "volume control", "Volume Control");
1420
1421        gnome_window_icon_set_from_file (GTK_WINDOW (data->about_dialog), GNOME_ICONDIR "gnome-mixer-applet.png");
1422       
1423        g_signal_connect (G_OBJECT (data->about_dialog), "destroy",
1424                          G_CALLBACK (gtk_widget_destroyed),
1425                          &data->about_dialog);
1426       
1427        gtk_widget_show (data->about_dialog);
1428}
1429
1430static void
1431mixer_help_cb (BonoboUIComponent *uic,
1432               MixerData         *data,
1433               const gchar       *verbname)
1434{
1435        GError *error = NULL;
1436
1437        gnome_help_display_on_screen (
1438                "mixer_applet2", NULL,
1439                gtk_widget_get_screen (data->applet),
1440                &error);
1441
1442        if (error) { /* FIXME: the user needs to see this error */
1443                g_print ("%s \n", error->message);
1444                g_error_free (error);
1445                error = NULL;
1446        }
1447}
1448
1449/* Dummy callback to get rid of a warning, for now. */
1450static void
1451mixer_mute_cb (BonoboUIComponent *uic,
1452               gpointer           data,
1453               const gchar       *verbname)
1454{
1455}
1456
1457#ifdef ALLOW_PREFERENCES
1458
1459static gboolean
1460cb_row_selected (GtkTreeSelection *selection, MixerData *data)
1461{
1462        PanelApplet *applet = PANEL_APPLET (data->applet);
1463        GtkTreeIter iter;
1464        GtkTreeModel *model;
1465        ChannelData *cdata;
1466       
1467        if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1468                return FALSE;
1469               
1470        gtk_tree_model_get (model, &iter, 1, &cdata, -1);
1471        if (cdata->channel == data->mixerchannel)
1472                return FALSE;           
1473       
1474        data->mixerchannel = cdata->channel;
1475        panel_applet_gconf_set_int (applet, "channel", data->mixerchannel, NULL);
1476}
1477
1478static void
1479show_help_cb (GtkWindow *dialog)
1480{
1481        GError *error = NULL;
1482        static GnomeProgram *applet_program = NULL;
1483       
1484        if (!applet_program) {
1485                int argc = 1;
1486                char *argv[2] = { "mixer_applet2" };
1487                applet_program = gnome_program_init ("mixer_applet2", VERSION,
1488                                                      LIBGNOME_MODULE, argc, argv,
1489                                                      GNOME_PROGRAM_STANDARD_PROPERTIES, NULL);
1490        }
1491
1492        gnome_help_display_desktop_on_screen (
1493                        applet_program, "mixer_applet2", "mixer_applet2", NULL,
1494                        gtk_widget_get_screen (GTK_WIDGET (dialog)),
1495                        &error);
1496
1497        if (error) {
1498                GtkWidget *error_dialog;
1499
1500                error_dialog = gtk_message_dialog_new (
1501                                NULL,
1502                                GTK_DIALOG_DESTROY_WITH_PARENT,
1503                                GTK_MESSAGE_ERROR,
1504                                GTK_BUTTONS_OK,
1505                                _("There was an error displaying help: %s"),
1506                                error->message);
1507
1508                g_signal_connect (error_dialog, "response",
1509                                  G_CALLBACK (gtk_widget_destroy),
1510                                  NULL);
1511
1512                gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
1513                gtk_window_set_screen (GTK_WINDOW (error_dialog),
1514                                       gtk_widget_get_screen (GTK_WIDGET (dialog)));
1515                gtk_widget_show (error_dialog);
1516                g_error_free (error);
1517        }
1518}
1519
1520static void
1521dialog_response (GtkDialog *dialog, gint id, MixerData *data)
1522{
1523        if (id == GTK_RESPONSE_HELP) {
1524                show_help_cb (GTK_WINDOW (dialog));
1525                return;
1526        }
1527       
1528        gtk_widget_destroy (GTK_WIDGET (dialog));
1529        data->prefdialog = NULL;
1530       
1531}
1532
1533static void
1534mixer_pref_cb (BonoboUIComponent *uic,
1535               MixerData         *data,
1536               const gchar       *verbname)
1537{
1538        GtkWidget *dialog;
1539        GtkWidget *vbox, *vbox1, *vbox2, *vbox3;
1540        GtkWidget *hbox;
1541        GtkWidget *label;
1542        GtkWidget *tree;
1543        GtkListStore *model;
1544        GtkWidget *scrolled;
1545        GtkTreeViewColumn *column;
1546        GtkCellRenderer *cell;
1547        gchar *tmp;
1548        GList *list = data->channels;
1549       
1550        if (data->prefdialog) {
1551                gtk_window_present (GTK_WINDOW (data->prefdialog));
1552                return;
1553        }
1554       
1555        dialog = gtk_dialog_new_with_buttons (_("Volume Control Preferences"), NULL,
1556                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
1557                                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1558                                                   GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1559                                                   NULL);
1560        gnome_window_icon_set_from_file (GTK_WINDOW (dialog), GNOME_ICONDIR "gnome-mixer-applet.png"); 
1561        gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE);
1562        gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
1563        gtk_window_set_screen (GTK_WINDOW (dialog),
1564                           gtk_widget_get_screen (GTK_WIDGET (data->applet)));
1565        gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
1566        gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 300);
1567        data->prefdialog = dialog;
1568                           
1569        vbox = GTK_DIALOG (dialog)->vbox;
1570        gtk_box_set_spacing (GTK_BOX (vbox), 2);
1571       
1572        vbox1 = gtk_vbox_new (FALSE, 18);
1573        gtk_container_set_border_width (GTK_CONTAINER (vbox1), 5);
1574        gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0);
1575       
1576        vbox2 = gtk_vbox_new (FALSE, 6);
1577        gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0);
1578       
1579        tmp = g_strdup_printf ("<b>%s</b>", _("Audio Channels"));
1580        label = gtk_label_new (NULL);
1581        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1582        gtk_label_set_markup (GTK_LABEL (label), tmp);
1583        g_free (tmp);
1584        gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0);
1585       
1586        hbox = gtk_hbox_new (FALSE, 0);
1587        gtk_box_pack_start (GTK_BOX (vbox2), hbox, TRUE, TRUE, 0);
1588       
1589        label = gtk_label_new ("    ");
1590        gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1591       
1592        vbox3 = gtk_vbox_new (FALSE, 6);
1593        gtk_box_pack_start (GTK_BOX (hbox), vbox3, TRUE, TRUE, 0);
1594       
1595        label = gtk_label_new_with_mnemonic (_("_Select channel to control:"));
1596        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1597        gtk_box_pack_start (GTK_BOX (vbox3), label, FALSE, FALSE, 0);
1598       
1599        scrolled = gtk_scrolled_window_new(NULL,NULL);
1600        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
1601                                             GTK_SHADOW_IN);
1602        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
1603                                                GTK_POLICY_AUTOMATIC,
1604                                                GTK_POLICY_AUTOMATIC);
1605        gtk_box_pack_start (GTK_BOX (vbox3), scrolled, TRUE, TRUE, 0);
1606       
1607        model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
1608        tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
1609        gtk_label_set_mnemonic_widget (GTK_LABEL (label), tree);
1610        gtk_container_add (GTK_CONTAINER (scrolled), tree);
1611        g_object_unref (G_OBJECT (model));
1612        cell = gtk_cell_renderer_text_new ();
1613        column = gtk_tree_view_column_new_with_attributes ("hello",
1614                                                           cell,
1615                                                           "text", 0,
1616                                                           NULL);
1617        gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1618        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE);
1619       
1620        while (list) {
1621                ChannelData *cdata = list->data;
1622                GtkTreeIter iter;
1623               
1624                gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1625                gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, _(cdata->name), 1, cdata, -1);
1626                if (cdata->channel == data->mixerchannel) {
1627                        GtkTreePath *path;
1628                        path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
1629                        gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree), path, NULL, FALSE);
1630                }
1631                list = g_list_next (list);
1632        }
1633
1634        if ( ! key_writable (PANEL_APPLET (data->applet), "channel")) {
1635                gtk_widget_set_sensitive (tree, FALSE);
1636                gtk_widget_set_sensitive (label, FALSE);
1637        }
1638
1639        g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree))),
1640                                  "changed",
1641                                   G_CALLBACK (cb_row_selected), data);
1642                                   
1643        g_signal_connect (G_OBJECT (dialog), "response",
1644                                  G_CALLBACK (dialog_response), data);
1645       
1646        gtk_widget_show_all (dialog);
1647       
1648}
1649#endif
1650
1651static void
1652mixer_ui_component_event (BonoboUIComponent            *comp,
1653                          const gchar                  *path,
1654                          Bonobo_UIComponent_EventType  type,
1655                          const gchar                  *state_string,
1656                          MixerData                    *data)
1657{
1658        gboolean state;
1659
1660        if (!strcmp (path, "Mute")) {
1661                if (!strcmp (state_string, "1")) {
1662                        state = TRUE;
1663                } else {
1664                        state = FALSE;
1665                }
1666
1667                if (state == data->mute) {
1668                        return;
1669                }
1670               
1671                data->mute = state;
1672
1673                if (data->mute) {
1674#ifdef SUN_API
1675                        set_mute_status (TRUE);
1676#else
1677                        setMixer (0, data);
1678#endif
1679                        mixer_update_image (data);
1680               
1681                               
1682                        gtk_tooltips_set_tip (data->tooltips,
1683                                              data->applet,
1684                                              _(access_name_mute),
1685                                              NULL);
1686                        if (gail_loaded) {
1687                                add_atk_namedesc (data->applet,
1688                                                  _(access_name_mute),
1689                                                  NULL);
1690                        }
1691                }
1692                else {
1693#ifdef SUN_API
1694                        set_mute_status (FALSE);
1695#else
1696                        setMixer (data->vol, data);
1697#endif
1698                        mixer_update_image (data);
1699                       
1700                       
1701                        gtk_tooltips_set_tip (data->tooltips,
1702                                              data->applet,
1703                                              _(access_name),
1704                                              NULL);
1705                        if (gail_loaded) {
1706                                add_atk_namedesc (data->applet,
1707                                                  _(access_name),
1708                                                  NULL);
1709                        }
1710                }
1711        }
1712}
1713
1714static void
1715get_channel (MixerData *data)
1716{
1717        PanelApplet *applet = PANEL_APPLET (data->applet);
1718        int num;
1719
1720        num = panel_applet_gconf_get_int (applet, "channel", NULL);
1721        num = CLAMP (num, 0, g_list_length (data->channels) - 1);
1722        data->mixerchannel = num;
1723}
1724
1725static void
1726get_prefs (MixerData *data)
1727{
1728        PanelApplet *applet = PANEL_APPLET (data->applet);
1729        gboolean mute;
1730       
1731        data->vol = panel_applet_gconf_get_int (applet, "vol", NULL);
1732        mute = panel_applet_gconf_get_bool (applet, "mute", NULL);
1733       
1734        if (data->vol < 0)
1735                return;
1736               
1737        data->mute = mute;
1738        if (data->mute) {
1739#ifdef SUN_API
1740                set_mute_status (TRUE);
1741#else
1742                setMixer (0, data);
1743#endif
1744                mixer_update_image (data);
1745               
1746                               
1747                gtk_tooltips_set_tip (data->tooltips,
1748                                              data->applet,
1749                                              _(access_name_mute),
1750                                              NULL);
1751                if (gail_loaded) {
1752                                add_atk_namedesc (data->applet,
1753                                                  _(access_name_mute),
1754                                                  NULL);
1755                }
1756        }
1757        else {
1758#ifdef SUN_API
1759                set_mute_status (FALSE);
1760#else
1761                setMixer (data->vol, data);
1762#endif
1763                mixer_update_image (data);
1764                       
1765               
1766                gtk_tooltips_set_tip (data->tooltips,
1767                                      data->applet,
1768                                      _(access_name),
1769                                      NULL);
1770                if (gail_loaded) {
1771                        add_atk_namedesc (data->applet,
1772                                          _(access_name),
1773                                          NULL);
1774                }
1775        }
1776}
1777
1778typedef struct
1779{
1780        char *stock_id;
1781        const guint8 *icon_data;
1782} MixerStockIcon;
1783   
1784static MixerStockIcon items[] =
1785    {
1786        { VOLUME_STOCK_MUTE, GNOME_ICONDIR"/mixer/volume-mute.png" },
1787        { VOLUME_STOCK_ZERO, GNOME_ICONDIR"/mixer/volume-zero.png" },
1788        { VOLUME_STOCK_MIN, GNOME_ICONDIR"/mixer/volume-min.png" },
1789        { VOLUME_STOCK_MED, GNOME_ICONDIR"/mixer/volume-medium.png" },
1790        { VOLUME_STOCK_MAX, GNOME_ICONDIR"/mixer/volume-max.png" }
1791};
1792
1793static void
1794register_mixer_stock_icons (GtkIconFactory *factory)
1795{
1796    GtkIconSource *source;
1797    int i;
1798
1799    source = gtk_icon_source_new ();
1800
1801    for (i = 0; i < G_N_ELEMENTS (items); i++) {
1802        GtkIconSet *icon_set;
1803
1804        gtk_icon_source_set_filename (source, items[i].icon_data);
1805
1806        icon_set = gtk_icon_set_new ();
1807        gtk_icon_set_add_source (icon_set, source);
1808
1809        gtk_icon_factory_add (factory, items[i].stock_id, icon_set);
1810
1811        gtk_icon_set_unref (icon_set);
1812    }
1813    gtk_icon_source_free (source);
1814}
1815
1816static void
1817mixer_init_stock_icons (void)
1818{
1819        GtkIconFactory *factory;
1820       
1821        if (icons_initialized)
1822                return;
1823
1824        volume_icon_size = gtk_icon_size_register ("panel-menu",
1825                                                   VOLUME_DEFAULT_ICON_SIZE,
1826                                                   VOLUME_DEFAULT_ICON_SIZE);
1827
1828        factory = gtk_icon_factory_new ();
1829        gtk_icon_factory_add_default (factory);
1830
1831        register_mixer_stock_icons (factory);
1832
1833        g_object_unref (factory);
1834
1835        icons_initialized = TRUE;
1836}
1837
1838const BonoboUIVerb mixer_applet_menu_verbs [] = {
1839        BONOBO_UI_UNSAFE_VERB ("RunMixer", mixer_start_gmix_cb),
1840        BONOBO_UI_UNSAFE_VERB ("Mute",     mixer_mute_cb),
1841        BONOBO_UI_UNSAFE_VERB ("Help",     mixer_help_cb),
1842        BONOBO_UI_UNSAFE_VERB ("About",    mixer_about_cb),
1843#ifdef ALLOW_PREFERENCES
1844        BONOBO_UI_UNSAFE_VERB ("Pref",    mixer_pref_cb),
1845#endif
1846        BONOBO_UI_VERB_END
1847};
1848
1849static void
1850applet_style_event_cb (GtkWidget *w, GtkStyle *prev_style, MixerData *user_data)
1851{       
1852        mixer_load_volume_images (user_data);
1853        mixer_update_image (user_data);
1854}
1855
1856mixer_applet_create (PanelApplet *applet)
1857{
1858        MixerData         *data;
1859        BonoboUIComponent *component;
1860        const gchar *device = NULL;
1861        gboolean retval;
1862#ifdef SUN_API
1863        gchar *ctl = NULL;
1864#endif
1865
1866        data = g_new0 (MixerData, 1);
1867        data->applet = GTK_WIDGET (applet);
1868        panel_applet_add_preferences (applet, "/schemas/apps/mixer_applet/prefs", NULL);
1869        panel_applet_set_flags (applet, PANEL_APPLET_EXPAND_MINOR);
1870        get_channel (data);
1871
1872#ifdef OSS_API
1873        /* /dev/sound/mixer for devfs */
1874        device = "/dev/mixer";
1875        retval = openMixer(device, data);
1876        if (!retval) {
1877                device = "/dev/sound/mixer";
1878                retval = openMixer (device, data);
1879        }
1880#endif
1881#ifdef SUN_API
1882        if (!(ctl = g_getenv("AUDIODEV")))
1883                ctl = "/dev/audio";
1884        device = g_strdup_printf("%sctl",ctl);
1885        retval = openMixer(device, data);
1886#endif
1887#ifdef AIX_API
1888        if (!(device = g_getenv("AUDIODEV")))
1889                device = "/dev/paud0";
1890        retval = openMixer(device, data);
1891#endif
1892#ifdef GSTREAMER_API
1893        retval = gstreamer_init (data);
1894#endif
1895
1896        mixer_init_stock_icons ();
1897        if (!retval) {
1898                device_present = FALSE;
1899        }
1900
1901        if (GTK_IS_ACCESSIBLE (gtk_widget_get_accessible(GTK_WIDGET(applet)))) {
1902                gail_loaded = TRUE;
1903        }
1904
1905        /* Try to find some mixers - sdtaudiocontrol is on Solaris and is needed
1906        ** because gnome-volume-meter doesn't work */
1907        if (run_mixer_cmd == NULL)
1908                run_mixer_cmd = g_find_program_in_path ("gnome-volume-control");
1909        if (run_mixer_cmd == NULL)
1910                run_mixer_cmd = g_find_program_in_path ("gmix");
1911        if (run_mixer_cmd == NULL)
1912                run_mixer_cmd = g_find_program_in_path ("sdtaudiocontrol");
1913       
1914        data->device = g_strdup (device);
1915        data->frame = gtk_frame_new (NULL);
1916        gtk_frame_set_shadow_type (GTK_FRAME (data->frame), GTK_SHADOW_NONE);
1917        gtk_container_add (GTK_CONTAINER (applet), data->frame);
1918       
1919        data->image = gtk_image_new ();
1920        gtk_container_add (GTK_CONTAINER (data->frame), data->image);
1921
1922        mixer_load_volume_images (data);
1923
1924        data->about_dialog = NULL;
1925
1926        data->tooltips = gtk_tooltips_new ();                                   
1927        g_object_ref (data->tooltips);
1928        gtk_object_sink (GTK_OBJECT (data->tooltips));
1929        gtk_tooltips_set_tip (data->tooltips,
1930                              data->applet,
1931                              _(access_name),
1932                              NULL);
1933        if (gail_loaded) {
1934                add_atk_namedesc(GTK_WIDGET(data->applet),
1935                                 _(access_name),
1936                                 _("The volume control lets you set the volume level for your desktop"));
1937        }
1938       
1939        g_signal_connect (data->applet,
1940                          "button-press-event",
1941                          (GCallback) applet_button_release_event_cb,
1942                          data);
1943       
1944        g_signal_connect (data->applet,
1945                          "key-press-event",
1946                          (GCallback) applet_key_press_event_cb,
1947                          data);
1948       
1949        g_signal_connect (data->applet,
1950                          "scroll-event",
1951                          (GCallback) applet_scroll_event_cb,
1952                          data);
1953
1954        g_signal_connect (data->applet,
1955                          "style-set",
1956                          (GCallback) applet_style_event_cb,
1957                          data);
1958       
1959        data->adj = GTK_ADJUSTMENT (
1960                gtk_adjustment_new (50,
1961                                    0.0,
1962                                    VOLUME_MAX,
1963                                    VOLUME_MAX/20,
1964                                    VOLUME_MAX/10,
1965                                    0.0));
1966
1967        g_signal_connect (data->adj,
1968                          "value-changed",
1969                          (GCallback) mixer_value_changed_cb,
1970                          data);
1971
1972        /* Get the initial volume. */
1973        get_prefs (data);
1974       
1975        if (!data->mute)
1976                data->vol = readMixer (data);
1977
1978        /* Install timeout handler, that keeps the applet up-to-date if the
1979         * volume is changed somewhere else.
1980         */
1981        data->timeout = g_timeout_add (500,
1982                                       (GSourceFunc) mixer_timeout_cb,
1983                                       data);
1984
1985        g_signal_connect (applet,
1986                          "destroy",
1987                          (GCallback) destroy_mixer_cb,
1988                          data);
1989
1990        g_signal_connect (applet,
1991                          "change_orient",
1992                          G_CALLBACK (applet_change_orient_cb),
1993                          data);
1994
1995        g_signal_connect (applet,
1996                          "change_size",
1997                          G_CALLBACK (applet_change_size_cb),
1998                          data);
1999
2000        g_signal_connect (applet,
2001                          "change_background",
2002                          G_CALLBACK (applet_change_background_cb),
2003                          data);
2004
2005        g_signal_connect (applet,
2006                          "size_allocate",
2007                          G_CALLBACK (applet_size_allocate),
2008                          data);
2009
2010        panel_applet_setup_menu_from_file (applet,
2011                                           NULL, /* opt_datadir */
2012                                           "GNOME_MixerApplet.xml",
2013                                           NULL,
2014                                           mixer_applet_menu_verbs,
2015                                           data);
2016
2017        if (panel_applet_get_locked_down (applet)) {
2018                BonoboUIComponent *popup_component;
2019
2020                popup_component = panel_applet_get_popup_component (applet);
2021
2022                bonobo_ui_component_set_prop (popup_component,
2023                                              "/commands/Pref",
2024                                              "hidden", "1",
2025                                              NULL);
2026        }
2027
2028
2029        component = panel_applet_get_popup_component (applet);
2030        g_signal_connect (component,
2031                          "ui-event",
2032                          (GCallback) mixer_ui_component_event,
2033                          data);
2034        if (data->mute)
2035                bonobo_ui_component_set_prop (component,
2036                                                      "/commands/Mute",
2037                                                      "state",
2038                                                      "1",
2039                                                      NULL);
2040        else
2041                bonobo_ui_component_set_prop (component,
2042                                                      "/commands/Mute",
2043                                                      "state",
2044                                                      "0",
2045                                                      NULL);
2046
2047        if (run_mixer_cmd == NULL)
2048                bonobo_ui_component_rm (component, "/popups/popup/RunMixer", NULL);
2049               
2050#ifndef ALLOW_PREFERENCES
2051        bonobo_ui_component_rm (component, "/popups/popup/Pref", NULL);
2052#endif
2053
2054        applet_change_orient_cb (GTK_WIDGET (applet),
2055                                 panel_applet_get_orient (applet),
2056                                 data);
2057        applet_change_size_cb (GTK_WIDGET (applet),
2058                               panel_applet_get_size (applet),
2059                               data);
2060
2061        mixer_update_slider (data);
2062        mixer_update_image (data);
2063
2064        gtk_widget_show_all (GTK_WIDGET (applet));
2065}
2066
2067static gboolean
2068mixer_applet_factory (PanelApplet *applet,
2069                      const gchar *iid,
2070                      gpointer     data)
2071{
2072        mixer_applet_create (applet);
2073
2074        return TRUE;
2075}
2076
2077PANEL_APPLET_BONOBO_FACTORY ("OAFIID:GNOME_MixerApplet_Factory",
2078                             PANEL_TYPE_APPLET,
2079                             "mixer_applet2",
2080                             "0",
2081                             mixer_applet_factory,
2082                             NULL)
2083 
2084/* Accessible name and description */
2085void
2086add_atk_namedesc (GtkWidget *widget, const gchar *name, const gchar *desc)
2087{
2088        AtkObject *atk_widget;
2089        atk_widget = gtk_widget_get_accessible(widget);
2090        atk_object_set_name(atk_widget, name);
2091        atk_object_set_description(atk_widget, desc);
2092}                                                                               
Note: See TracBrowser for help on using the repository browser.