from typing import Optional
from comtypes import GUID
from comtypes.automation import VT_BOOL, VT_LPWSTR, VT_EMPTY
from comtypes.persist import STGM_READWRITE
from pycaw.api.mmdeviceapi import PROPERTYKEY
from pycaw.api.mmdeviceapi.depend import PROPVARIANT
from pycaw.utils import AudioUtilities
#Hardcoded values
LISTEN_SETTING_GUID = "{24DBB0FC-9311-4B3D-9CF0-18FF155639D4}"
CHECKBOX_PID = 1
LISTENING_DEVICE_PID = 0
#Values you can change:
microphone_name = 'Microphone (USB-MIC)'
listening_device_name = 'Speakers (5- GSA 70 Main Audio)' #Set to None to use the default playback device
enable_listening = False
def main():
store = get_device_store(microphone_name)
if store is None:
print("failed to open property store")
exit(1)
set_listening_checkbox(store, enable_listening)
set_listening_device(store, listening_device_name)
#Write to the checkbox property
def set_listening_checkbox(property_store, value:bool):
checkbox_pk = PROPERTYKEY()
checkbox_pk.fmtid = GUID(LISTEN_SETTING_GUID)
checkbox_pk.pid = CHECKBOX_PID
new_value = PROPVARIANT(VT_BOOL)
new_value.union.boolVal = value
property_store.SetValue(checkbox_pk, new_value)
#Write to the device property
def set_listening_device(property_store, output_device_name:Optional[str]):
if output_device_name is not None:
listening_device_guid = get_GUID_from_name(output_device_name)
else:
listening_device_guid = None
device_pk = PROPERTYKEY()
device_pk.fmtid = GUID(LISTEN_SETTING_GUID)
device_pk.pid = LISTENING_DEVICE_PID
if listening_device_guid is not None:
new_value = PROPVARIANT(VT_LPWSTR)
new_value.union.pwszVal = listening_device_guid
else:
new_value = PROPVARIANT(VT_EMPTY)
property_store.SetValue(device_pk, new_value)
#Gets the device store from the device name
def get_device_store(device_name:str):
device_guid = get_GUID_from_name(device_name)
enumerator = AudioUtilities.GetDeviceEnumerator()
dev = enumerator.GetDevice(device_guid)
store = dev.OpenPropertyStore(STGM_READWRITE)
return store
#This is just a helper method to turn a device name into a GUID.
def get_GUID_from_name(device_name:str) -> str:
input_devices = get_list_of_active_coreaudio_devices("input")
for device in input_devices:
if device.FriendlyName == device_name:
return device.id
output_devices = get_list_of_active_coreaudio_devices("output")
for device in output_devices:
if device.FriendlyName == device_name:
return device.id
raise ValueError("Device not found!")
#Helper method to get all (active) devices
def get_list_of_active_coreaudio_devices(device_type:str) -> list:
import comtypes
from pycaw.pycaw import AudioUtilities, IMMDeviceEnumerator, EDataFlow, DEVICE_STATE
from pycaw.constants import CLSID_MMDeviceEnumerator
if device_type != "output" and device_type != "input":
raise ValueError("Invalid audio device type.")
if device_type == "output":
EDataFlowValue = EDataFlow.eRender.value
else:
EDataFlowValue = EDataFlow.eCapture.value
# Code to enumerate devices adapted from https://github.com/AndreMiras/pycaw/issues/50#issuecomment-981069603
devices = list()
device_enumerator = comtypes.CoCreateInstance(
CLSID_MMDeviceEnumerator,
IMMDeviceEnumerator,
comtypes.CLSCTX_INPROC_SERVER)
if device_enumerator is None:
raise ValueError("Couldn't find any devices.")
collection = device_enumerator.EnumAudioEndpoints(EDataFlowValue, DEVICE_STATE.ACTIVE.value)
if collection is None:
raise ValueError("Couldn't find any devices.")
count = collection.GetCount()
for i in range(count):
dev = collection.Item(i)
if dev is not None:
if not ": None" in str(AudioUtilities.CreateDevice(dev)):
devices.append(AudioUtilities.CreateDevice(dev))
return devices
if name == "main":
main()
24dbb0fc-9311-4b3d-9cf0-18ff155639d4
I am getting some interesting results pointing to theMMDevice API
. See for example here. – Robert Mar 17 '20 at 08:53Spy++
for that to get PID from the HWND you showed.) I'd guess that it'd be one of rundll32 procs. 2. Then use a debugger (x64Dbg
would work) and set a breakpoint onntdll.ZwSetValueKey
in that proc (before that UI is shown). You may want to make a conditional bp to catch when your registry value is written. It will be in a 2nd parameter asUNICODE_STRING*
. 3. Run the proc until bp triggers. 4. After then just walk thru the code with your debugger and see what they are doing there. No guesswork needed. – c00000fd Mar 18 '20 at 21:32RegSetValue
then doubleclick the entry and inspect the callstack which might be easier than using a debugger... – Remko Mar 21 '20 at 21:53GetSystemMetrics
/SystemParametersInfo
/WM_SETTINGSCHANGE
, but it turns out those are not used for what you want/need. What I am wondering is whether you are actually interested in achieving that functionality or whether it's important to you how to reverse engineer such stuff?! Given we're on RE.SE I'd assume it's the latter, but I'd like to know as I think that in this case it might be possible to substitute reverse engineering with knowledge about Win32 programming. It would be normal, at least, for that configuration change to be broadcast somehow. – 0xC0000022L May 06 '20 at 13:30mixerSetControlDetails
andMIXERCONTROL
look relevant, for example. – 0xC0000022L May 06 '20 at 13:57