**Linux: volume control** [bll] 2016-12-19 Builds a stand-alone executable or loadable Tcl stub for Linux. Uses the pulse audio library. ====== load [file join [pwd] pulseaudio64[info sharedlibextension]] set sinkdict [pulseaudio getsinklist] ; # get the list of sinks # the sink dictionary has: # sinkname - string, sink name used in pulseaudio. # default - boolean, if true, is the default sink # index - int, index number # description - string, human readable description of the sink dict for {sink info} $sinkdict { if { [dict get $info default] } { set sinkname $sink break } } set vol [pulseaudio $sinkname] ; # get the volume for the specified sink incr vol -10 set newvol [pulseaudio $sinkname $vol] ; # set the volume, always returns the current volume. ====== '''pamkvol.sh''' ====== #!/bin/sh slext=.so tv=8.6 arch=`uname -m` bits=64 case $arch in i?86*) bits=32 ;; esac inc=/usr/include/tcl${tv} lv=$tv ${CC:-cc} -O -m${bits} -o pavolume${bits} \ -UPA_TCL_INTERFACE pulseaudio.c \ -lpulse -lm ${CC:-cc} -O -m${bits} -shared -fPIC -o pulseaudio${bits}${slext} \ -DPA_TCL_INTERFACE -I$inc -DUSE_TCL_STUBS pulseaudio.c \ -ltclstub${lv} -lpulse -lm ====== '''pulseaudio.c''' ====== /* * Copyright 2016 Brad Lanam Walnut Creek CA USA * MIT License * * pulse audio * getsinklist * getvolume * setvolume * * References: * https://github.com/cdemoulins/pamixer/blob/master/pulseaudio.cc * https://freedesktop.org/software/pulseaudio/doxygen/ */ #ifdef PA_TCL_INTERFACE # include #endif #include #include #include #include #include #include typedef struct { char *defname; void *tcldata; } getSinkData_t; void serverInfoCallback ( pa_context *context, const pa_server_info *i, void *userdata) { char **defname = (char **) userdata; *defname = strdup (i->default_sink_name); } void sinkVolCallback ( pa_context *context, const pa_sink_info *i, int eol, void *userdata) { pa_cvolume *vol = (pa_cvolume *) userdata; if (eol != 0) { return; } memcpy (vol, &(i->volume), sizeof (pa_cvolume)); } void connCallback (pa_context* pacontext, void* state) { int *stateptr = (int *) state; if (pacontext == NULL) { *stateptr = -1; return; } switch (pa_context_get_state (pacontext)) { case PA_CONTEXT_READY: *stateptr = 0; break; case PA_CONTEXT_FAILED: *stateptr = -1; break; case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_TERMINATED: break; } } void volSetCallback ( pa_context* context, int success, void* userdata) { } void waitop ( pa_mainloop *pamainloop, pa_operation *op) { int retval; while (pa_operation_get_state (op) == PA_OPERATION_RUNNING) { pa_mainloop_iterate (pamainloop, 1, &retval); } pa_operation_unref(op); } int argcheck (int argc, char *action) { int rc; rc = 0; if (argc < 2 || argc > 4 || (strcmp (action, "getsinklist") != 0 && strcmp (action, "getvolume") != 0 && strcmp (action, "setvolume") != 0) || (argc != 2 && strcmp (action, "getsinklist") == 0) || (argc != 3 && strcmp (action, "getvolume") == 0) || (argc != 4 && strcmp (action, "setvolume") == 0)) { rc = 1; } return rc; } #ifndef PA_TCL_INTERFACE void getSinkCallback ( pa_context *context, const pa_sink_info *i, int eol, void *userdata) { getSinkData_t *sinkdata = (getSinkData_t *) userdata; char *defflag = "no"; if (eol != 0) { return; } if (strcmp (i->name, sinkdata->defname) == 0) { defflag = "yes"; } printf ("%s %d {%s} {%s}\n", defflag, i->index, i->name, i->description); } int main (int argc, char *argv[]) { char *sinkname; int vol; int rc; if (argcheck (argc, argv[1]) != 0) { fprintf (stderr, "Usage: %s {getsinklist|getvolume |setvolume }\n", argv[0]); return 1; } vol = 0; sinkname = ""; if (argc > 2) { sinkname = argv[2]; } if (argc > 3) { vol = atoi (argv[3]); } rc = process (argv[1], sinkname, &vol, NULL); if (strcmp (argv[1], "getsinklist") != 0) { printf ("%d\n", vol); } return rc; } #endif /* ! PA_TCL_INTERFACE */ #ifdef PA_TCL_INTERFACE typedef struct { Tcl_Interp *interp; Tcl_Obj *dictObj; } tclSinkData_t; static void addStringToDict (Tcl_Interp *interp, Tcl_Obj *dict, const char *nm, const char *val) { Tcl_Obj *tempObj1; Tcl_Obj *tempObj2; tempObj1 = Tcl_NewStringObj (nm, -1); tempObj2 = Tcl_NewStringObj (val, -1); Tcl_DictObjPut (interp, dict, tempObj1, tempObj2); } static void addIntToDict (Tcl_Interp *interp, Tcl_Obj *dict, const char *nm, int val) { Tcl_Obj *tempObj1; Tcl_Obj *tempObj2; tempObj1 = Tcl_NewStringObj (nm, -1); tempObj2 = Tcl_NewIntObj (val); Tcl_DictObjPut (interp, dict, tempObj1, tempObj2); } static void addBooleanToDict (Tcl_Interp *interp, Tcl_Obj *dict, const char *nm, int val) { Tcl_Obj *tempObj1; Tcl_Obj *tempObj2; tempObj1 = Tcl_NewStringObj (nm, -1); tempObj2 = Tcl_NewBooleanObj (val); Tcl_DictObjPut (interp, dict, tempObj1, tempObj2); } void getSinkCallback ( pa_context *context, const pa_sink_info *i, int eol, void *userdata) { getSinkData_t *sinkdata = (getSinkData_t *) userdata; tclSinkData_t *tcldata; char *defflag = "no"; Tcl_Obj *dictObj; Tcl_Obj *tempDictObj; Tcl_Obj *nameKey; if (eol != 0) { return; } if (strcmp (i->name, sinkdata->defname) == 0) { defflag = "yes"; } tcldata = sinkdata->tcldata; tempDictObj = Tcl_NewDictObj(); addBooleanToDict (tcldata->interp, tempDictObj, "default", (defflag == "yes" ? 1 : 0)); addIntToDict (tcldata->interp, tempDictObj, "index", i->index); addStringToDict (tcldata->interp, tempDictObj, "description", i->description); nameKey = Tcl_NewStringObj (i->name, -1); Tcl_DictObjPut (tcldata->interp, tcldata->dictObj, nameKey, tempDictObj); /* printf ("%s %d {%s} {%s}\n", defflag, i->index, i->name, i->description); */ } int pulseaudioObjCmd ( ClientData cd, Tcl_Interp* interp, int objc, Tcl_Obj * const objv[] ) { char* action; char* sinkname; int lt1; int lt2; int rvol; int vol; int rc; tclSinkData_t tcldata; Tcl_Obj *dictObj = Tcl_NewDictObj (); /* for UTF-8 locales */ if (objc >= 2) { action = Tcl_GetStringFromObj(objv[1], <1); /* action */ } if (argcheck (objc, action) != 0) { Tcl_WrongNumArgs(interp, 1, objv, "action ?sink-name? ?vol?"); return TCL_ERROR; } sinkname = NULL; vol = 0; rvol = 0; dictObj = NULL; if (strcmp (action, "getsinklist") != 0) { sinkname = Tcl_GetStringFromObj(objv[2], <2); /* sink-name */ if (strcmp (action, "setvolume") == 0) { rc = Tcl_GetIntFromObj(interp, objv[3], &vol); if (rc != TCL_OK) { return TCL_ERROR; } rvol = vol; } } else { dictObj = Tcl_NewDictObj (); tcldata.interp = interp; tcldata.dictObj = dictObj; } rc = process (action, sinkname, &rvol, &tcldata); if (rc != 0) { return TCL_ERROR; } if (strcmp (action, "getsinklist") == 0) { Tcl_SetObjResult (interp, dictObj); } else { Tcl_SetObjResult (interp, Tcl_NewIntObj (rvol)); } return TCL_OK; } int Pulseaudio_Init (Tcl_Interp *interp) { Tcl_Encoding utf; #ifdef USE_TCL_STUBS if (!Tcl_InitStubs(interp,"8.3",0)) { return TCL_ERROR; } #else if (!Tcl_PkgRequire(interp,"Tcl","8.3",0)) { return TCL_ERROR; } #endif Tcl_CreateObjCommand(interp,"pulseaudio", pulseaudioObjCmd, (ClientData) NULL, NULL); Tcl_PkgProvide(interp,"pulseaudio","0.1"); return TCL_OK; } #endif /* PA_TCL_INTERFACE */ int process (char *action, char *sinkname, int *vol, void *tcldata) { pa_mainloop *pamainloop; pa_mainloop_api *paapi; pa_context *pacontext; pa_operation *op; pa_cvolume pacvolume; int retval; int state; int tvol; pamainloop = pa_mainloop_new(); paapi = pa_mainloop_get_api (pamainloop); pacontext = pa_context_new (paapi, "ballroomdj"); if (pacontext == NULL) { pa_mainloop_free (pamainloop); return -1; } pa_context_set_state_callback (pacontext, &connCallback, &state); state = 1; pa_context_connect (pacontext, NULL, PA_CONTEXT_NOFLAGS, NULL); while (state == 1) { pa_mainloop_iterate (pamainloop, 1, &retval); } if (state < 0) { pa_context_disconnect (pacontext); pa_context_unref (pacontext); pacontext = NULL; pa_mainloop_free (pamainloop); return -1; } if (strcmp (action, "getsinklist") == 0) { char *defsinkname; getSinkData_t sinkdata; defsinkname = NULL; op = pa_context_get_server_info ( pacontext, &serverInfoCallback, &defsinkname); waitop (pamainloop, op); sinkdata.defname = defsinkname; sinkdata.tcldata = tcldata; op = pa_context_get_sink_info_list (pacontext, &getSinkCallback, &sinkdata); waitop (pamainloop, op); if (defsinkname != NULL) { free (defsinkname); } } else { pa_volume_t avgvol; pa_cvolume pavol; op = pa_context_get_sink_info_by_name ( pacontext, sinkname, &sinkVolCallback, &pavol); waitop (pamainloop, op); if (strcmp (action, "setvolume") == 0) { pa_cvolume *nvol; tvol = (int) round ((double) *vol * (double) PA_VOLUME_NORM / 100.0); if (tvol > PA_VOLUME_MAX) { tvol = PA_VOLUME_MAX; } nvol = pa_cvolume_set (&pavol, pavol.channels, tvol); op = pa_context_set_sink_volume_by_name ( pacontext, sinkname, nvol, volSetCallback, NULL); waitop (pamainloop, op); } op = pa_context_get_sink_info_by_name ( pacontext, sinkname, &sinkVolCallback, &pavol); waitop (pamainloop, op); avgvol = pa_cvolume_avg (&pavol); *vol = (int) round ((double) avgvol / (double) PA_VOLUME_NORM * 100.0); } pa_context_disconnect (pacontext); pa_context_unref (pacontext); pacontext = NULL; pa_mainloop_free (pamainloop); return 0; } ====== [WJG] (20/12/16) Gnocl has a wrapping about the native Gtk Volume Button widget. Although the Gtk offers no direct control over the audio output this is still easily obtained by using aumixer or pulseaudio. The following snippet shows how to do this with aumixer. Adjustments can be made via mouse click and drag or with the mouseWheel button. Feedback on new level settings displayed in the widget tooltip. ====== proc getVol {} { set vol [exec amixer sget Master] set a [string first \[ $vol] set b [string first \] $vol] return "0.[string range $vol $a+1 $b-2]" } gnocl::window -child [gnocl::volumeButton -prelightBackgroundColor green -value [getVol] -onValueChanged { exec amixer sset 'Master' [expr 100*%v] } ] ====== <>Linux