Volume control plugin settings

The standard panel of LXDE - http://wiki.lxde.org/en/LXPanel
Locked
delirium
Posts: 30
Joined: Tue Oct 07, 2008 11:15 am

Volume control plugin settings

Post by delirium »

How can I manage to control another alsa control with the plugin instead of Master?
delirium
Posts: 30
Joined: Tue Oct 07, 2008 11:15 am

Re: Volume control plugin settings

Post by delirium »

Fixed it for me with changing the source...
:roll:

Update:

improved my changes to a little configuration UI...but till now chane sonly take effect on lxpanel restart (or plugin reload)

Code: Select all

/**
 * Copyright (c) 2008 LxDE Developers, see the file AUTHORS for details.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <gtk/gtk.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <alsa/asoundlib.h>
#include <poll.h>
#include "panel.h"
#include "misc.h"
#include "plugin.h"
#include "dbg.h"

#define ICONS_VOLUME PACKAGE_DATA_DIR "/lxpanel/images/volume.png"
#define ICONS_MUTE PACKAGE_DATA_DIR "/lxpanel/images/mute.png"

typedef struct {
    Plugin* plugin;
    GtkWidget *mainw;
    GtkWidget *tray_icon;
    GtkWidget *dlg;
    GtkTooltips* tooltips;
    GtkWidget *vscale;
    guint vscale_handler;
    GtkWidget* mute_check;
    guint mute_handler;
    snd_mixer_t *mixer;
    snd_mixer_selem_id_t *sid;
    snd_mixer_elem_t *master_element;
    char *master_name;
    long alsa_min_vol, alsa_max_vol;
    long level;
    int mute;
    int show;
    gboolean mixer_evt_idle;
} volume_t;


/* ALSA */

static int asound_read(volume_t *vol);

static void asound_write(volume_t *vol, int volume);

static gboolean find_element(volume_t *vol, const char *ename)
{
    for (vol->master_element=snd_mixer_first_elem(vol->mixer);vol->master_element;vol->master_element=snd_mixer_elem_next(vol->master_element)) {
        snd_mixer_selem_get_id(vol->master_element, vol->sid);
        if (!snd_mixer_selem_is_active(vol->master_element))
            continue;

        if (strcmp(ename, snd_mixer_selem_id_get_name(vol->sid))==0) {
            return TRUE;
        }
    }

    return FALSE;
}

static void update_display(volume_t* vol)
{
    /* mute status */
    snd_mixer_selem_get_playback_switch(vol->master_element, 0, &vol->mute);

    if (vol->mute==0)
        gtk_image_set_from_file(vol->tray_icon, ICONS_MUTE);
    else
        gtk_image_set_from_file(vol->tray_icon, ICONS_VOLUME);

    g_signal_handler_block( vol->mute_check, vol->mute_handler );
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(vol->mute_check), !vol->mute );
    g_signal_handler_unblock( vol->mute_check, vol->mute_handler );

    /* volume */
    vol->level = asound_read(vol);

    if( vol->vscale )
    {
        g_signal_handler_block( vol->vscale, vol->vscale_handler );
        gtk_range_set_value(GTK_RANGE(vol->vscale), vol->level);
        g_signal_handler_unblock( vol->vscale, vol->vscale_handler );
    }
}

/* NOTE by PCMan:
 * This is magic! Since ALSA uses its own machanism to handle this part.
 * After polling of mixer fds, it requires that we should call
 * snd_mixer_handle_events to clear all pending mixer events.
 * However, when using the glib IO channels approach, we don't have
 * poll() and snd_mixer_poll_descriptors_revents(). Due to the design of
 * glib, on_mixer_event() will be called for every fd whose status was
 * changed. So, after each poll(), it's called for several times,
 * not just once. Therefore, we cannot call snd_mixer_handle_events()
 * directly in the event handler. Otherwise, it will get called for
 * several times, which might clear unprocessed pending events in the queue.
 * So, here we call it once in the event callback for the first fd.
 * Then, we don't call it for the following fds. After all fds with changed
 * status are handled, we remove this restriction in an idle handler.
 * The next time the event callback is involked for the first fs, we can
 * call snd_mixer_handle_events() again. Racing shouldn't happen here
 * because the idle handler has the same priority as the io channel callback.
 * So, io callbacks for future pending events should be in the next gmain
 * iteration, and won't be affected.
 */
static gboolean reset_mixer_evt_idle( volume_t* vol )
{
    vol->mixer_evt_idle = 0;
    return FALSE;
}

static gboolean on_mixer_event( GIOChannel* channel, GIOCondition cond, volume_t *vol )
{
    if( 0 == vol->mixer_evt_idle )
    {
        vol->mixer_evt_idle = g_idle_add_full( G_PRIORITY_DEFAULT, (GSourceFunc)reset_mixer_evt_idle, vol, NULL );
        snd_mixer_handle_events( vol->mixer );
    }

    if( cond & G_IO_IN )
    {
        /* the status of mixer is changed. update of display is needed. */
        update_display( vol );
    }
    if( cond & G_IO_HUP )
    {
        /* FIXME: This means there're some problems with alsa. */

        return FALSE;
    }

    return TRUE;
}

static gboolean asound_init(volume_t *vol)
{
    int i, n_fds;
    struct pollfd *fds;

    snd_mixer_selem_id_alloca(&vol->sid);
    snd_mixer_open(&vol->mixer, 0);
    snd_mixer_attach(vol->mixer, "default");
    snd_mixer_selem_register(vol->mixer, NULL, NULL);
    snd_mixer_load(vol->mixer);

    /* Find Master element */
	if (!find_element(vol,  vol->master_name))
      if (!find_element(vol, "Master"))
        if (!find_element(vol, "Front"))
            if (!find_element(vol, "PCM"))
            	if (!find_element(vol, "LineOut"))
                    return FALSE;
                   


    snd_mixer_selem_get_playback_volume_range(vol->master_element, &vol->alsa_min_vol, &vol->alsa_max_vol);

    snd_mixer_selem_set_playback_volume_range(vol->master_element, 0, 100);

    /* listen to events from alsa */
    n_fds = snd_mixer_poll_descriptors_count( vol->mixer );
    fds = g_new0( struct pollfd, n_fds );

    snd_mixer_poll_descriptors( vol->mixer, fds, n_fds );
    for( i = 0; i < n_fds; ++i )
    {
        /* g_debug("fd=%d", fds[i]); */
        GIOChannel* channel = g_io_channel_unix_new( fds[i].fd );
        g_io_add_watch( channel, G_IO_IN|G_IO_HUP, on_mixer_event, vol );
        g_io_channel_unref( channel );
    }
    g_free( fds );
    return TRUE;
}

int asound_read(volume_t *vol)
{
    long aleft, aright;
    /* Left */
    snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, &aleft);
    /* Right */
    snd_mixer_selem_get_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, &aright);

    return (aleft + aright) >> 1;
}

void asound_write(volume_t *vol, int volume)
{
    snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_LEFT, volume);
    snd_mixer_selem_set_playback_volume(vol->master_element, SND_MIXER_SCHN_FRONT_RIGHT, volume);
}

static gboolean focus_out_event(GtkWidget *widget, GdkEvent *event, volume_t *vol)
{
    gtk_widget_hide(vol->dlg);
    vol->show = 0;
    return FALSE;
}

static gboolean tray_icon_press(GtkWidget *widget, GdkEventButton *event, volume_t *vol)
{
    if( event->button == 3 )  /* right button */
    {
        GtkMenu* popup = lxpanel_get_panel_menu( vol->plugin->panel, vol->plugin, FALSE );
        gtk_menu_popup( popup, NULL, NULL, NULL, NULL, event->button, event->time );
        return TRUE;
    }

    if (vol->show==0) {
        gtk_window_set_position(GTK_WINDOW(vol->dlg), GTK_WIN_POS_MOUSE);
        gtk_widget_show_all(vol->dlg);
        vol->show = 1;
    } else {
        gtk_widget_hide(vol->dlg);
        vol->show = 0;
    }
    return TRUE;
}

static void on_vscale_value_changed(GtkRange *range, volume_t *vol)
{
    asound_write(vol, gtk_range_get_value(range));
}

static void on_vscale_scrolled( GtkScale* scale, GdkEventScroll *evt, volume_t* vol )
{
    gdouble val = gtk_range_get_value((GtkRange*)scale);
    switch( evt->direction )
    {
    case GDK_SCROLL_UP:
        val += 2;
        break;
    case GDK_SCROLL_DOWN:
        val -= 2;
        break;
    }
    gtk_range_set_value((GtkRange*)scale, CLAMP((int)val, 0, 100) );
}

static void click_mute(GtkWidget *widget, volume_t *vol)
{
    int chn;

    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
        gtk_image_set_from_file(vol->tray_icon, ICONS_MUTE);
        for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
            snd_mixer_selem_set_playback_switch(vol->master_element, chn, 0);
        }
    } else {
        gtk_image_set_from_file(vol->tray_icon, ICONS_VOLUME);
        for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
            snd_mixer_selem_set_playback_switch(vol->master_element, chn, 1);
        }
    }
}

static void panel_init(Plugin *p)
{
    volume_t *vol = p->priv;
    GtkWidget *scrolledwindow;
    GtkWidget *viewport;
    GtkWidget *box;
    GtkWidget *frame;

    /* set show flags */
    vol->show = 0;

    /* create a new window */
    vol->dlg = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_decorated(GTK_WINDOW(vol->dlg), FALSE);
    gtk_container_set_border_width(GTK_CONTAINER(vol->dlg), 5);
    gtk_window_set_default_size(GTK_WINDOW(vol->dlg), 80, 140);
    gtk_window_set_skip_taskbar_hint(GTK_WINDOW(vol->dlg), TRUE);
    gtk_window_set_skip_pager_hint(GTK_WINDOW(vol->dlg), TRUE);
    gtk_window_set_type_hint(GTK_WINDOW(vol->dlg), GDK_WINDOW_TYPE_HINT_DIALOG);

    /* Focus-out signal */
    g_signal_connect (G_OBJECT (vol->dlg), "focus_out_event",
              G_CALLBACK (focus_out_event), vol);

    scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow), 0);
    gtk_widget_show (scrolledwindow);
    gtk_container_add (GTK_CONTAINER (vol->dlg), scrolledwindow);
    GTK_WIDGET_UNSET_FLAGS (scrolledwindow, GTK_CAN_FOCUS);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_NONE);

    viewport = gtk_viewport_new (NULL, NULL);
    gtk_container_add (GTK_CONTAINER (scrolledwindow), viewport);
    gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
    gtk_widget_show(viewport);

    /* create frame */
    frame = gtk_frame_new(_("Volume"));
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
    gtk_container_add(GTK_CONTAINER(viewport), frame);

    /* create box */
    box = gtk_vbox_new(FALSE, 0);

    /* create controller */
    vol->vscale = gtk_vscale_new(GTK_ADJUSTMENT(gtk_adjustment_new(asound_read(vol), 0, 100, 0, 0, 0)));
    gtk_scale_set_draw_value(GTK_SCALE(vol->vscale), FALSE);
    gtk_range_set_inverted(GTK_RANGE(vol->vscale), TRUE);

    vol->vscale_handler = g_signal_connect ((gpointer) vol->vscale, "value_changed",
                                  G_CALLBACK (on_vscale_value_changed),
                                  vol);
    g_signal_connect( vol->vscale, "scroll-event", G_CALLBACK(on_vscale_scrolled), vol );

    vol->mute_check = gtk_check_button_new_with_label(_("Mute"));
    snd_mixer_selem_get_playback_switch(vol->master_element, 0, &vol->mute);

    vol->mute_handler = g_signal_connect ((gpointer) vol->mute_check, "toggled",
                                  G_CALLBACK (click_mute),
                                  vol);

    gtk_box_pack_start(GTK_BOX(box), vol->vscale, TRUE, TRUE, 0);
    gtk_box_pack_end(GTK_BOX(box), vol->mute_check, FALSE, FALSE, 0);
    gtk_container_add(GTK_CONTAINER(frame), box);

    /* setting background to default */
    gtk_widget_set_style(viewport, p->panel->defstyle);
}

static void
volumealsa_destructor(Plugin *p)
{
    volume_t *vol = (volume_t *) p->priv;

    ENTER;

    if( vol->mixer_evt_idle )
        g_source_remove( vol->mixer_evt_idle );

    if (vol->dlg)
        gtk_widget_destroy(vol->dlg);

    g_free(vol);
    RET();
}

static int
volumealsa_constructor(Plugin *p, char **fp)
{
    volume_t *vol;
    line s;
    GdkPixbuf *icon;
    GtkWidget *image;
    GtkIconTheme* theme;
    GtkIconInfo* info;

    ENTER;
    s.len = 256;
    vol = g_new0(volume_t, 1);
    vol->plugin = p;
    g_return_val_if_fail(vol != NULL, 0);
    p->priv = vol;



//---
    
    if( fp )
    {
        while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
            if (s.type == LINE_NONE) {
                ERR( "volumealsa: illegal token %s\n", s.str);
               // return -1;
            }
            if (s.type == LINE_VAR) {
                if (!g_ascii_strcasecmp(s.t[0], "AlsaMix"))
				{
                    vol->master_name = g_strdup(s.t[1]);
                    
                }else{
                    ERR( "volumealsa: unknown var %s\n", s.t[0]);
                   // return -1;
                }
            } else {
                ERR( "volumealsa: illegal in this context %s\n", s.str);
                //return -1;
            }
        }
    }
//---
	 
    /* initializing */
    if (!asound_init(vol))
        RET(1);
panel_init(p);
    /* main */
    vol->mainw = gtk_event_box_new();

    gtk_widget_add_events(vol->mainw, GDK_BUTTON_PRESS_MASK);
    gtk_widget_set_size_request( vol->mainw, 24, 24 );

    g_signal_connect(G_OBJECT(vol->mainw), "button-press-event",
                         G_CALLBACK(tray_icon_press), vol);

    /* tray icon */
    vol->tray_icon = gtk_image_new();
    update_display( vol );

    gtk_container_add(GTK_CONTAINER(vol->mainw), vol->tray_icon);

    gtk_widget_show_all(vol->mainw);

    vol->tooltips = p->panel->tooltips;;
#if GLIB_CHECK_VERSION( 2, 10, 0 )
    g_object_ref_sink( vol->tooltips );
#else
    g_object_ref( vol->tooltips );
    gtk_object_sink( vol->tooltips );
#endif

    /* FIXME: display current level in tooltip. ex: "Volume Control: 80%"  */
    gtk_tooltips_set_tip (vol->tooltips, vol->mainw, _("Volume control"), NULL);

    /* store the created plugin widget in plugin->pwid */
    p->pwid = vol->mainw;

    RET(1);
}



static void apply_config( Plugin* p )
{

//check and re-init
		
}

static void volumealsa_config( Plugin *p, GtkWindow* parent )
{
    GtkWidget* dlg;
    volume_t *dc = (volume_t *)p->priv;
    dlg = create_generic_config_dlg( _(p->class->name),
                                     GTK_WIDGET(parent),
                                    (GSourceFunc) apply_config, (gpointer) p,
                                     _("Alsa Mixer"), &dc->master_name , G_TYPE_STRING,
                                     
                                     NULL );
    gtk_window_present( GTK_WINDOW(dlg) );
}



static void save_config( Plugin* p, FILE* fp )
{
    volume_t *dc = (volume_t *)p->priv;
    lxpanel_put_str( fp, "AlsaMix", dc->master_name );
}

PluginClass volumealsa_plugin_class = {
    fname: NULL,
    count: 0,

    type : "volumealsa",
    name : N_("Volume Control"),
    version: "1.1",
    description : "Display and control volume for ALSA",

    constructor : volumealsa_constructor,
    destructor  : volumealsa_destructor,
    config : volumealsa_config,
    save : save_config
};
Locked