Skip to content

Commit bcaef2c

Browse files
authored
Merge pull request #839 from abdulkadiryugruk/master
Fix volume control for Electron apps (Discord, Brave) & Crash Fixes on macOS Tahoe
2 parents 86ca21c + f363351 commit bcaef2c

6 files changed

Lines changed: 109 additions & 30 deletions

File tree

BGMApp/BGMApp/BGMBackgroundMusicDevice.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,14 @@ BGMBackgroundMusicDevice::ResponsibleBundleIDsOf(CACFString inParentBundleID)
237237
{ "hu.mplayerhq.mplayerosx.extended",
238238
{ "ch.sttz.mplayerosx.extended.binaries.officialsvn" } },
239239
// Discord
240-
{ "com.hnc.Discord", { "com.hnc.Discord.helper" } },
240+
{ "com.hnc.Discord",
241+
{ "com.hnc.Discord.helper",
242+
"com.hnc.Discord.helper.Renderer",
243+
"com.hnc.Discord.helper.Plugin" } },
244+
// Brave
245+
{ "com.brave.Browser",
246+
{ "com.brave.Browser.helper",
247+
"com.brave.Browser.helper.plugin" } },
241248
// Skype
242249
{ "com.skype.skype", { "com.skype.skype.Helper" } },
243250
// Google Chrome

BGMApp/BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// MockAudioObjects.cpp
1818
// BGMAppUnitTests
1919
//
20-
// Copyright © 2020 Kyle Neideck
20+
// Copyright © 2020, 2025 Kyle Neideck
2121
//
2222

2323
// Self Include
@@ -26,6 +26,9 @@
2626
// PublicUtility Includes
2727
#include "CACFString.h"
2828

29+
// STL Includes
30+
#include <vector>
31+
2932

3033
// static
3134
MockAudioObjects::MockDeviceMap MockAudioObjects::sDevices;
@@ -87,9 +90,9 @@ std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(CFStringRef in
8790
{
8891
// Convert inUID to a std::string.
8992
UInt32 uidCStringLen = CACFString::GetStringByteLength(inUID) + 1;
90-
char uidCString[uidCStringLen];
91-
CACFString::GetCString(inUID, uidCString, uidCStringLen);
92-
std::string uid = std::string(uidCString);
93+
std::vector<char> uidCString(uidCStringLen);
94+
CACFString::GetCString(inUID, uidCString.data(), uidCStringLen);
95+
std::string uid = std::string(uidCString.data());
9396

9497
return GetAudioDevice(uid);
9598
}

BGMDriver/BGMDriver/BGM_Device.cpp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// BGM_Device.cpp
1818
// BGMDriver
1919
//
20-
// Copyright © 2016, 2017, 2019 Kyle Neideck
20+
// Copyright © 2016, 2017, 2019, 2025 Kyle Neideck
2121
// Copyright © 2017 Andrew Tonner
2222
// Copyright © 2019 Gordon Childs
2323
// Copyright © 2020 Aleksey Yurkevich
@@ -40,6 +40,7 @@
4040
#include "CADispatchQueue.h"
4141
#include "CAException.h"
4242
#include "CACFArray.h"
43+
#include "CACFDictionary.h"
4344
#include "CACFString.h"
4445
#include "CADebugMacros.h"
4546
#include "CAHostTimeBase.h"
@@ -1001,6 +1002,74 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
10011002
};
10021003
}
10031004

1005+
// Validates inAppVolumes for the kAudioDeviceCustomPropertyAppVolumes property as described in
1006+
// BGM_Types.h. Throws CAException(kAudioHardwareIllegalOperationError) if invalid.
1007+
static void ValidateAppVolumesProperty(const CACFArray& inAppVolumes)
1008+
{
1009+
UInt32 theCount = inAppVolumes.GetNumberItems();
1010+
1011+
for(UInt32 i = 0; i < theCount; i++)
1012+
{
1013+
// Each element must be a CFDictionary.
1014+
CFTypeRef theElement = nullptr;
1015+
bool didGetValue = inAppVolumes.GetCFType(i, theElement);
1016+
ThrowIf(!didGetValue || theElement == nullptr,
1017+
CAException(kAudioHardwareIllegalOperationError),
1018+
"BGM_Device::ValidateAppVolumesProperty: Could not get element from array");
1019+
ThrowIf(CFGetTypeID(theElement) != CFDictionaryGetTypeID(),
1020+
CAException(kAudioHardwareIllegalOperationError),
1021+
"BGM_Device::ValidateAppVolumesProperty: Element is not a CFDictionary");
1022+
1023+
CACFDictionary theDict(static_cast<CFDictionaryRef>(theElement), false);
1024+
1025+
// Check for ProcessID. Must be a CFNumber if present.
1026+
CFTypeRef thePIDValue = nullptr;
1027+
bool hasPID = theDict.GetCFType(CFSTR(kBGMAppVolumesKey_ProcessID), thePIDValue);
1028+
if(hasPID && thePIDValue != nullptr)
1029+
{
1030+
ThrowIf(CFGetTypeID(thePIDValue) != CFNumberGetTypeID(),
1031+
CAException(kAudioHardwareIllegalOperationError),
1032+
"BGM_Device::ValidateAppVolumesProperty: ProcessID is not a CFNumber");
1033+
}
1034+
1035+
// Check for BundleID. Must be a CFString if present.
1036+
CFTypeRef theBundleIDValue = nullptr;
1037+
bool hasBundleID = theDict.GetCFType(CFSTR(kBGMAppVolumesKey_BundleID), theBundleIDValue);
1038+
if(hasBundleID && theBundleIDValue != nullptr)
1039+
{
1040+
ThrowIf(CFGetTypeID(theBundleIDValue) != CFStringGetTypeID(),
1041+
CAException(kAudioHardwareIllegalOperationError),
1042+
"BGM_Device::ValidateAppVolumesProperty: BundleID is not a CFString");
1043+
}
1044+
1045+
// At least one of ProcessID or BundleID must be present.
1046+
ThrowIf(!hasPID && !hasBundleID,
1047+
CAException(kAudioHardwareIllegalOperationError),
1048+
"BGM_Device::ValidateAppVolumesProperty: Neither ProcessID nor BundleID present");
1049+
1050+
// Check RelativeVolume. Must be a CFNumber in [0, 100] if present.
1051+
SInt32 theVolume;
1052+
bool hasVolume = theDict.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theVolume);
1053+
if(hasVolume)
1054+
{
1055+
ThrowIf(theVolume < kAppRelativeVolumeMinRawValue ||
1056+
theVolume > kAppRelativeVolumeMaxRawValue,
1057+
CAException(kAudioHardwareIllegalOperationError),
1058+
"BGM_Device::ValidateAppVolumesProperty: RelativeVolume out of range");
1059+
}
1060+
1061+
// Check PanPosition. Must be a CFNumber in [-100, 100] if present.
1062+
SInt32 thePan;
1063+
bool hasPan = theDict.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePan);
1064+
if(hasPan)
1065+
{
1066+
ThrowIf(thePan < kAppPanLeftRawValue || thePan > kAppPanRightRawValue,
1067+
CAException(kAudioHardwareIllegalOperationError),
1068+
"BGM_Device::ValidateAppVolumesProperty: PanPosition out of range");
1069+
}
1070+
}
1071+
}
1072+
10041073
void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
10051074
{
10061075
switch(inAddress.mSelector)
@@ -1092,6 +1161,8 @@ void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
10921161

10931162
CACFArray array(arrayRef, false);
10941163

1164+
ValidateAppVolumesProperty(array);
1165+
10951166
bool propertyWasChanged = false;
10961167

10971168
CAMutex::Locker theStateLocker(mStateMutex);
@@ -1104,7 +1175,7 @@ void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
11041175
{
11051176
Throw(CAException(kAudioHardwareIllegalOperationError));
11061177
}
1107-
1178+
11081179
if(propertyWasChanged)
11091180
{
11101181
// Send notification

BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// BGM_ClientMap.cpp
1818
// BGMDriver
1919
//
20-
// Copyright © 2016, 2017, 2019 Kyle Neideck
20+
// Copyright © 2016, 2017, 2019, 2025 Kyle Neideck
2121
// Copyright © 2017 Andrew Tonner
2222
//
2323

@@ -264,6 +264,9 @@ std::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(pid_t inAppPid) {
264264
}
265265

266266
std::vector<BGM_Client*> * _Nullable BGM_ClientMap::GetClients(CACFString inAppBundleID) {
267+
if(!inAppBundleID.IsValid()) {
268+
return nullptr;
269+
}
267270
return GetClientsFromMap(mClientMapByBundleIDShadow, inAppBundleID);
268271
}
269272

@@ -335,12 +338,12 @@ bool BGM_ClientMap::SetClientsRelativeVolume(pid_t searchKey, Float32 inRelative
335338
bool BGM_ClientMap::SetClientsRelativeVolume(CACFString searchKey, Float32 inRelativeVolume)
336339
{
337340
bool didChangeVolume = false;
338-
341+
339342
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
340-
343+
341344
auto theSetVolumesInShadowMapsFunc = [&] {
342345
// Look up the clients for the key and update their volumes
343-
346+
344347
auto theClients = GetClients(searchKey);
345348
if(theClients != nullptr)
346349
{
@@ -389,9 +392,9 @@ bool BGM_ClientMap::SetClientsPanPosition(pid_t searchKey, SInt32 inPanPosition)
389392
bool BGM_ClientMap::SetClientsPanPosition(CACFString searchKey, SInt32 inPanPosition)
390393
{
391394
bool didChangePanPosition = false;
392-
395+
393396
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
394-
397+
395398
auto theSetPansInShadowMapsFunc = [&] {
396399
// Look up the clients for the key and update their pan positions
397400
auto theClients = GetClients(searchKey);

BGMDriver/BGMDriver/DeviceClients/BGM_ClientMap.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// BGM_ClientMap.h
1818
// BGMDriver
1919
//
20-
// Copyright © 2016 Kyle Neideck
20+
// Copyright © 2016, 2025 Kyle Neideck
2121
//
2222

2323
#ifndef __BGMDriver__BGM_ClientMap__
@@ -131,11 +131,13 @@ class BGM_ClientMap
131131
// Returns true if a client for PID inAppPID was found and its relative volume changed.
132132
bool SetClientsRelativeVolume(pid_t inAppPID, Float32 inRelativeVolume);
133133
// Returns true if a client for bundle ID inAppBundleID was found and its relative volume changed.
134+
// inAppBundleID may contain a null CFStringRef, in which case it returns false.
134135
bool SetClientsRelativeVolume(CACFString inAppBundleID, Float32 inRelativeVolume);
135136

136137
// Returns true if a client for PID inAppPID was found and its pan position changed.
137138
bool SetClientsPanPosition(pid_t inAppPID, SInt32 inPanPosition);
138139
// Returns true if a client for bundle ID inAppBundleID was found and its pan position changed.
140+
// inAppBundleID may contain a null CFStringRef, in which case it returns false.
139141
bool SetClientsPanPosition(CACFString inAppBundleID, SInt32 inPanPosition);
140142

141143
void StartIONonRT(UInt32 inClientID) { UpdateClientIOStateNonRT(inClientID, true); }

BGMDriver/BGMDriver/DeviceClients/BGM_Clients.cpp

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// BGM_Clients.cpp
1818
// BGMDriver
1919
//
20-
// Copyright © 2016, 2017, 2019 Kyle Neideck
20+
// Copyright © 2016, 2017, 2019, 2025 Kyle Neideck
2121
// Copyright © 2017 Andrew Tonner
2222
//
2323

@@ -26,6 +26,7 @@
2626

2727
// Local Includes
2828
#include "BGM_Types.h"
29+
#include "BGM_Utils.h"
2930
#include "BGM_PlugIn.h"
3031

3132
// PublicUtility Includes
@@ -324,26 +325,22 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
324325
// Get the app's PID from the dict
325326
pid_t theAppPID;
326327
bool didFindPID = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), theAppPID);
327-
328+
328329
// Get the app's bundle ID from the dict
329330
CACFString theAppBundleID;
330331
theAppBundleID.DontAllowRelease();
331332
theAppVolume.GetCACFString(CFSTR(kBGMAppVolumesKey_BundleID), theAppBundleID);
332-
333-
ThrowIf(!didFindPID && !theAppBundleID.IsValid(),
334-
BGM_InvalidClientRelativeVolumeException(),
335-
"BGM_Clients::SetClientsRelativeVolumes: App volume was sent without PID or bundle ID for app");
336-
333+
334+
BGMAssert(didFindPID || theAppBundleID.IsValid(),
335+
"BGM_Clients::SetClientsRelativeVolumes: No PID or bundle ID");
336+
(void)didFindPID; // Suppress unused variable warning in release builds.
337+
337338
bool didGetVolume;
338339
{
339340
SInt32 theRawRelativeVolume;
340341
didGetVolume = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_RelativeVolume), theRawRelativeVolume);
341-
342+
342343
if (didGetVolume) {
343-
ThrowIf(didGetVolume && (theRawRelativeVolume < kAppRelativeVolumeMinRawValue || theRawRelativeVolume > kAppRelativeVolumeMaxRawValue),
344-
BGM_InvalidClientRelativeVolumeException(),
345-
"BGM_Clients::SetClientsRelativeVolumes: Relative volume for app out of valid range");
346-
347344
// Apply the volume curve to the raw volume
348345
//
349346
// mRelativeVolumeCurve uses the default kPow2Over1Curve transfer function, so we also multiply by 4 to
@@ -372,10 +369,6 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
372369
SInt32 thePanPosition;
373370
didGetPanPosition = theAppVolume.GetSInt32(CFSTR(kBGMAppVolumesKey_PanPosition), thePanPosition);
374371
if (didGetPanPosition) {
375-
ThrowIf(didGetPanPosition && (thePanPosition < kAppPanLeftRawValue || thePanPosition > kAppPanRightRawValue),
376-
BGM_InvalidClientPanPositionException(),
377-
"BGM_Clients::SetClientsRelativeVolumes: Pan position for app out of valid range");
378-
379372
if(mClientMap.SetClientsPanPosition(theAppPID, thePanPosition))
380373
{
381374
didChangeAppVolumes = true;

0 commit comments

Comments
 (0)