**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; } ====== <>Linux