How to query Windows mute control?

Started by CWD_in_NM, April 07, 2023, 07:08:51 PM

Previous topic - Next topic

CWD_in_NM


Hello,
Windows 10 Home 22H2, WinBatch 2023A, Dll 6.23awa
I have been banging my head against the wall for a couple of days and I am ready to declare defeat. Part of a script that I am working on uses the Windows 10 control panel Sound applet. I have been able to do everything that I need except one thing. I am trying to use the Speakers mute control. I launch the applet, mmsys.cpl, using ShellExecute. Once open I use the following sequence to get to the mute control. Playback tab > Speakers list item > Properties button. This opens the Speakers Properties window. The Levels tab has the speakers volume control and mute button. I have been able to get the handle for the mute button and use cClickButton to operate it. What I have not been able to figure out is if there is a way to query the button to check if it is muted or unmuted. I tried cEnableState but that shows whether the control itself is enabled/disabled, it does not return the mute/unmute state. I am not a programmer, so other software languages are out of my league. Can anyone tell me if there is a way to query the button in WinBatch?
Thanks,
Charlie

stanl


You want to query the audio status as either muted or un-muted, not just set the status [which can be done with WB]

While there is a Win32_SoundDevice that can be queried in WB and it has properties for Status and StatusInfo - they would not be helpful. There is C# code that could be compiled for use in WB; Jim has a lot of experience doing that. I may give it a shot and produce some WB code you can work with.


C# code for anyone else with an interest


using System.Runtime.InteropServices;
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioEndpointVolume {
  // f(), g(), ... are unused COM method slots. Define these if you care
  int f(); int g(); int h(); int i();
  int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext);
  int j();
  int GetMasterVolumeLevelScalar(out float pfLevel);
  int k(); int l(); int m(); int n();
  int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext);
  int GetMute(out bool pbMute);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
  int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
  int f(); // Unused
  int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public class Audio {
  static IAudioEndpointVolume Vol() {
    var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
    IMMDevice dev = null;
    Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
    IAudioEndpointVolume epv = null;
    var epvid = typeof(IAudioEndpointVolume).GUID;
    Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
    return epv;
  }
  public static float Volume {
    get {float v = -1; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out v)); return v;}
    set {Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(value, System.Guid.Empty));}
  }
  public static bool Mute {
    get { bool mute; Marshal.ThrowExceptionForHR(Vol().GetMute(out mute)); return mute; }
    set { Marshal.ThrowExceptionForHR(Vol().SetMute(value, System.Guid.Empty)); }
  }
}





And if you want a quick WB function to toggle mute and unmute, I dug this up from the vault
Code (WINBATCH) Select


#DefineFunction togglemute()
retval=""
oS = CreateObject("MSScriptControl.ScriptControl")
oS.Language = "VBScript"
oS.AllowUI = @FALSE
retval=oS.Eval(: "CreateObject(""WScript.Shell"").SendKeys(chr(173))")
oS = 0
Return(retval)
#EndFunction

togglemute()
Exit

td

Quote from: CWD_in_NM on April 07, 2023, 07:08:51 PM

Hello,
Windows 10 Home 22H2, WinBatch 2023A, Dll 6.23awa
I have been banging my head against the wall for a couple of days and I am ready to declare defeat. Part of a script that I am working on uses the Windows 10 control panel Sound applet. I have been able to do everything that I need except one thing. I am trying to use the Speakers mute control. I launch the applet, mmsys.cpl, using ShellExecute. Once open I use the following sequence to get to the mute control. Playback tab > Speakers list item > Properties button. This opens the Speakers Properties window. The Levels tab has the speakers volume control and mute button. I have been able to get the handle for the mute button and use cClickButton to operate it. What I have not been able to figure out is if there is a way to query the button to check if it is muted or unmuted. I tried cEnableState but that shows whether the control itself is enabled/disabled, it does not return the mute/unmute state. I am not a programmer, so other software languages are out of my league. Can anyone tell me if there is a way to query the button in WinBatch?
Thanks,
Charlie

This is an example of a common problem when attempting to automate a Windows UI. The problem is conflating the visual representation with the underlying device state. The only change in the button is the bitmap that is rendered in its frame. The control's state does not change.

Of the top, I can't think of a good way to detect the state of a sound device. The APIs involve are mostly COM early bind which means they do not have COM automation interfaces and I am not aware of any WMI interfaces that give the information. That said the above may be more a statement of my ignorance and some convenient approach may actually exist.

Also, checking for registry settings that reflect the state of sound devices is one other thing to consider.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on April 08, 2023, 09:19:00 AM

Of the top, I can't think of a good way to detect the state of a sound device.


I thought you might jump all over the C# code I placed in my response... either trash it or say it is a possibility. The OP has WB 2023A, so I'm sure compiling in-memory should not be a problem. OP may want to respond with a specific reason to query as perhaps the frequency of the query would be of concern.

CWD_in_NM

Hi All,
Thanks for the replies. I was afraid that this might not be as easy as it sounded. My default computer setup is the monitor and PC speakers. The script I am working on changes the video to HDMI output and audio to S/PDIF output and they are routed to my entertainment center receiver. When I am done watching, the script switches everything back to default. I had been doing all of this manually and decided to automate it. I have everything working except for the mute button. When I was working through the audio part of the script, I had one run where I had no audio at all. Thought I had done something wrong in the script but it turned out that I had the audio muted, DUH. That was when I started working on the mute button. I can just throw in a message at the front end of the script to remind myself to check mute state. I was just hoping to automate that as well.
Charlie

stanl

Quote from: CWD_in_NM on April 08, 2023, 07:12:20 PM
The script I am working on changes the video to HDMI output and audio to S/PDIF output and they are routed to my entertainment center receiver. When I am done watching, the script switches everything back to default.


For a 'non-programmer' I'd say pretty impressive. There is a likely PS set of cmdlets that might be interesting to look at. Winbatch can run PS but it is often frowned on when brought up here. Not sure applicable to your situation since the alternative is not a deal breaker. But, for the record :  https://github.com/frgnca/AudioDeviceCmdlets


td

Quote from: stanl on April 08, 2023, 09:32:33 AM

I thought you might jump all over the C# code I placed in my response... either trash it or say it is a possibility. The OP has WB 2023A, so I'm sure compiling in-memory should not be a problem. OP may want to respond with a specific reason to query as perhaps the frequency of the query would be of concern.

Since CWD_in_NM said he wasn't a programmer and didn't sound too interested in doing a deep dive into C# programming, I more or less ignored it. All the C# business is doing is calling the early bind COM interfaces of the device APIs that I mentioned. I still suspect that the registry ~could~ offer a solution as many device drivers record states in the registry. However, it would require a lot of work to determine if and where that information is located. Come to think of it, the effort is probably not worth the bother either as there is no guarantee of success. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on April 09, 2023, 08:46:28 AM
Come to think of it, the effort is probably not worth the bother either as there is no guarantee of success.


Agreed, and I alluded to that, not in so many words. For fun, I installed the PS stuff and the functions worked fine - except I don't use HDMI and only have standard Realtec speaker and headphone.  ??? . I see no business use as we use either Webex or MS Teams and the apps pretty much take care of sounds.

CWD_in_NM

Hi All,
Had some other stuff going on, finally got time to work on the script some more. Downloaded and installed AudioDeviceCmdlets today and played around with them. Was able to use the required Get and Set functions in WinBatch, via Powershell. Now I can check the mute state and if needed toggle the state with the cmdlets from my script. As far as I can tell, everything in the script seems to be working as intended.
Thanks again for all of the help, much appreciated.
Charlie

cssyphus

Hi Charlie,

Sorry I didn't see this sooner.  I've been using a handy freeware utility called SetVol, written by a guy up in Ottawa Canada.
https://www.rlatour.com/setvol/#