Windows XP: volume control

2018-5-18 : Now available on SourceForge: https://sourceforge.net/projects/volume-controls-cmdline/files/

bll 2016-12-19 Finding the code to handle Windows XP volume was a bit difficult. Builds a stand-alone executable or loadable Tcl stub.

load [file join [pwd] winxpvolume[info sharedlibextension]]
set vol [winxpvolume] ; # get the volume
incr vol -10
set newvol [winxpvolume $vol] ; # set the volume, always returns the current volume.

winxpmkvol.sh

#!/bin/bash

gcc -m32 -static-libgcc -UWINXP_TCL_INTERFACE -o winxpvolume.exe \
    -I$HOME/local-32/include -DUSE_TCL_STUBS winxpvolume.c \
    -L$HOME/local-32/lib -lwinmm
gcc -m32 -shared -static-libgcc -DWINXP_TCL_INTERFACE -o winxpvolume.dll \
    -I$HOME/local-32/include -DUSE_TCL_STUBS winxpvolume.c \
    -L$HOME/local-32/lib -ltclstub86 -lwinmm

winxpvolume.c

/*
 * Copyright 2016 Brad Lanam Walnut Creek CA US
 * MIT License
 *
 * Reference:
 * https://www.codeproject.com/kb/audio-video/mixersetcontroldetails.aspx?display=printall&fid=16481&df=90&mpp=25&noise=3&sort=position&view=quick&select=2765918
 */

#ifdef WINXP_TCL_INTERFACE
# include <tcl.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>

int
process (int set, int vol)
{
  MMRESULT result;
  HMIXER   hMixer;

  result = mixerOpen(&hMixer, MIXER_OBJECTF_MIXER, 0, 0, 0);
  MIXERLINE ml = {0};
  ml.cbStruct = sizeof(MIXERLINE);
  ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
  result = mixerGetLineInfo((HMIXEROBJ) hMixer,
     &ml, MIXER_GETLINEINFOF_COMPONENTTYPE);

  MIXERLINECONTROLS mlc = {0};
  MIXERCONTROL mc = {0};
  mlc.cbStruct = sizeof(MIXERLINECONTROLS);
  mlc.dwLineID = ml.dwLineID;
  mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
  mlc.cControls = 1;
  mlc.pamxctrl = &mc;
  mlc.cbmxctrl = sizeof(MIXERCONTROL);
  result = mixerGetLineControls((HMIXEROBJ) hMixer,
      &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

  if (set) {
    vol = round ((double) vol / 100.0 * 65535.0);
    MIXERCONTROLDETAILS mcd = {0};
    MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};
    mcdu.dwValue = vol;
    mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
    mcd.hwndOwner = 0;
    mcd.dwControlID = mc.dwControlID;
    mcd.paDetails = &mcdu;
    mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
    mcd.cChannels = 1;
    result = mixerSetControlDetails((HMIXEROBJ) hMixer,
        &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
  }

  MIXERCONTROLDETAILS mcd = {0};
  MIXERCONTROLDETAILS_UNSIGNED mcdu = {0};
  mcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
  mcd.hwndOwner = 0;
  mcd.dwControlID = mc.dwControlID;
  mcd.paDetails = &mcdu;
  mcd.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
  mcd.cChannels = 1;
  result = mixerGetControlDetails((HMIXEROBJ) hMixer,
      &mcd, MIXER_SETCONTROLDETAILSF_VALUE);
  vol = (int) round ((double) mcdu.dwValue / 65535.0 * 100.0);
  return vol;
}

#ifndef WINXP_TCL_INTERFACE

int
main (int argc, char *argv[])
{
  int set;
  int vol;

  set = 0;
  if (argc > 1) {
    set = 1;
    vol = atoi(argv[1]);
  }

  vol = process (set, vol);
  printf ("%d\n", vol);
  fflush (stdout);
  return 0;
}

#endif

#ifdef WINXP_TCL_INTERFACE

int winxpvolumeObjCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int           set;
  int           vol;
  int           rc;

  if (objc != 1 && objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "?vol?");
    return TCL_ERROR;
  }
  set = 0;
  if (objc == 2) {
    rc = Tcl_GetIntFromObj(interp, objv[1], &vol);
    if (rc != TCL_OK) {
      return TCL_ERROR;
    }
    set = 1;
  }
  vol = process (set, vol);
  Tcl_SetObjResult (interp, Tcl_NewIntObj (vol));
  return TCL_OK;
}

int
Winxpvolume_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,"winxpvolume", winxpvolumeObjCmd, (ClientData) NULL, NULL);
  Tcl_PkgProvide(interp,"winxpvolume","0.1");

  return TCL_OK;
}

#endif /* WINXP_TCL_INTERFACE */