Fixed bug not displaying system volume when muted

This commit is contained in:
Andrea Alberti 2025-09-29 03:46:20 +02:00
parent 5940962b26
commit a64ad6af92
4 changed files with 181 additions and 154 deletions

View File

@ -241,7 +241,7 @@ static NSTimeInterval updateSystemVolumeInterval=0.1f;
static NSString * const kHelperBundleIDSuffix = @"Helper";
- (NSString *)helperBundleID {
return [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:kHelperBundleIDSuffix];
return [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:kHelperBundleIDSuffix];
}
- (IBAction)terminate:(id)sender
@ -285,58 +285,58 @@ static NSString * const kHelperBundleIDSuffix = @"Helper";
- (void)updateStartAtLoginMenuItem
{
BOOL enabled = [self StartAtLogin];
NSMenuItem* menuItem = [self.statusMenu itemWithTag:START_AT_LOGIN_ID];
[menuItem setState:enabled ? NSControlStateValueOn : NSControlStateValueOff];
BOOL enabled = [self StartAtLogin];
NSMenuItem* menuItem = [self.statusMenu itemWithTag:START_AT_LOGIN_ID];
[menuItem setState:enabled ? NSControlStateValueOn : NSControlStateValueOff];
}
- (void)setStartAtLogin:(BOOL)enabled savePreferences:(BOOL)savePreferences
{
NSString *helperBundleID = @"io.alberti42.VolumeControlHelper";
NSString *helperBundleID = @"io.alberti42.VolumeControlHelper";
if (@available(macOS 13.0, *)) {
SMAppService *service = [SMAppService loginItemServiceWithIdentifier:helperBundleID];
NSError *error = nil;
if (@available(macOS 13.0, *)) {
SMAppService *service = [SMAppService loginItemServiceWithIdentifier:helperBundleID];
NSError *error = nil;
if (enabled) {
if (service.status != SMAppServiceStatusEnabled) {
if (![service registerAndReturnError:&error]) {
NSLog(@"[Volume Control] Error registering login item: %@", error.localizedDescription);
}
}
} else {
if (service.status != SMAppServiceStatusNotRegistered) {
if (![service unregisterAndReturnError:&error]) {
NSLog(@"[Volume Control] Error unregistering login item: %@", error.localizedDescription);
}
}
}
} else {
// Legacy fallback (macOS 12 and older)
if (!SMLoginItemSetEnabled((__bridge CFStringRef)helperBundleID, enabled)) {
NSLog(@"[Volume Control] SMLoginItemSetEnabled failed.");
}
}
if (enabled) {
if (service.status != SMAppServiceStatusEnabled) {
if (![service registerAndReturnError:&error]) {
NSLog(@"[Volume Control] Error registering login item: %@", error.localizedDescription);
}
}
} else {
if (service.status != SMAppServiceStatusNotRegistered) {
if (![service unregisterAndReturnError:&error]) {
NSLog(@"[Volume Control] Error unregistering login item: %@", error.localizedDescription);
}
}
}
} else {
// Legacy fallback (macOS 12 and older)
if (!SMLoginItemSetEnabled((__bridge CFStringRef)helperBundleID, enabled)) {
NSLog(@"[Volume Control] SMLoginItemSetEnabled failed.");
}
}
if (savePreferences) {
[preferences setBool:enabled forKey:@"StartAtLoginPreference"];
}
if (savePreferences) {
[preferences setBool:enabled forKey:@"StartAtLoginPreference"];
}
[self updateStartAtLoginMenuItem];
[self updateStartAtLoginMenuItem];
}
- (bool)StartAtLogin
{
NSString *helperBundleID = @"io.alberti42.VolumeControlHelper";
NSString *helperBundleID = @"io.alberti42.VolumeControlHelper";
if (@available(macOS 13.0, *)) {
SMAppService *service = [SMAppService loginItemServiceWithIdentifier:helperBundleID];
return (service.status == SMAppServiceStatusEnabled ||
service.status == SMAppServiceStatusRequiresApproval);
} else {
return [preferences boolForKey:@"StartAtLoginPreference"];
}
if (@available(macOS 13.0, *)) {
SMAppService *service = [SMAppService loginItemServiceWithIdentifier:helperBundleID];
return (service.status == SMAppServiceStatusEnabled ||
service.status == SMAppServiceStatusRequiresApproval);
} else {
return [preferences boolForKey:@"StartAtLoginPreference"];
}
}
- (void)wasAuthorized
@ -490,8 +490,11 @@ static NSString * const kHelperBundleIDSuffix = @"Helper";
else if (runningPlayerPtr == doppler)
[self setSpotifyVolume:[runningPlayerPtr currentVolume]];
if (_LockSystemAndPlayerVolume || runningPlayerPtr == systemAudio)
[self setSystemVolume:[runningPlayerPtr currentVolume]];
// Update system UI if system volume is affected or when locked
if (_LockSystemAndPlayerVolume || runningPlayerPtr == systemAudio) {
[self setSystemVolume:[systemAudio currentVolume]];
}
}
}
@ -785,9 +788,9 @@ static NSString * const kHelperBundleIDSuffix = @"Helper";
- (IBAction)toggleStartAtLogin:(id)sender
{
BOOL currentlyEnabled = [self StartAtLogin];
[self setStartAtLogin:!currentlyEnabled savePreferences:YES];
[self updateStartAtLoginMenuItem];
BOOL currentlyEnabled = [self StartAtLogin];
[self setStartAtLogin:!currentlyEnabled savePreferences:YES];
[self updateStartAtLoginMenuItem];
}
- (void) setUseAppleCMDModifier:(bool)enabled

View File

@ -17,136 +17,155 @@
-(AudioDeviceID) getDefaultOutputDevice
{
AudioObjectPropertyAddress getDefaultOutputDevicePropertyAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
AudioObjectPropertyAddress getDefaultOutputDevicePropertyAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
AudioDeviceID defaultOutputDeviceID;
UInt32 volumedataSize = sizeof(defaultOutputDeviceID);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&getDefaultOutputDevicePropertyAddress,
0, NULL,
&volumedataSize, &defaultOutputDeviceID);
AudioDeviceID defaultOutputDeviceID;
UInt32 volumedataSize = sizeof(defaultOutputDeviceID);
OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&getDefaultOutputDevicePropertyAddress,
0, NULL,
&volumedataSize, &defaultOutputDeviceID);
if(kAudioHardwareNoError != result)
{
NSLog(@"Cannot find default output device!");
}
if(kAudioHardwareNoError != result)
{
NSLog(@"Cannot find default output device!");
}
return defaultOutputDeviceID;
return defaultOutputDeviceID;
}
- (void)setCurrentVolume:(double)currentVolume
{
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioObjectPropertyAddress mutePropertyAddress = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioObjectPropertyAddress mutePropertyAddress = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
Float32 volume = (Float32)(currentVolume / 100.);
UInt32 dataSize;
Float32 volume = (Float32)(currentVolume / 100.);
UInt32 dataSize;
if (volume == 0) {
// Mute the device
UInt32 mute = 1;
dataSize = sizeof(mute);
OSStatus result = AudioObjectSetPropertyData(defaultOutputDeviceID,
&mutePropertyAddress,
0, NULL,
dataSize, &mute);
if (result != noErr) {
NSLog(@"Failed to mute device 0x%0x", defaultOutputDeviceID);
}
} else {
// Unmute the device
UInt32 mute = 0;
dataSize = sizeof(mute);
AudioObjectSetPropertyData(defaultOutputDeviceID,
&mutePropertyAddress,
0, NULL,
dataSize, &mute);
if (volume == 0) {
// Mute the device
UInt32 mute = 1;
dataSize = sizeof(mute);
OSStatus result = AudioObjectSetPropertyData(defaultOutputDeviceID,
&mutePropertyAddress,
0, NULL,
dataSize, &mute);
if (result != noErr) {
NSLog(@"Failed to mute device 0x%0x", defaultOutputDeviceID);
}
} else {
// Unmute the device
UInt32 mute = 0;
dataSize = sizeof(mute);
AudioObjectSetPropertyData(defaultOutputDeviceID,
&mutePropertyAddress,
0, NULL,
dataSize, &mute);
// Set the volume
dataSize = sizeof(volume);
OSStatus result = AudioObjectSetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
dataSize, &volume);
if (result != noErr) {
NSLog(@"Failed to set volume for device 0x%0x", defaultOutputDeviceID);
}
}
// Set the volume
dataSize = sizeof(volume);
OSStatus result = AudioObjectSetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
dataSize, &volume);
if (result != noErr) {
NSLog(@"Failed to set volume for device 0x%0x", defaultOutputDeviceID);
}
}
}
- (bool) isMuted
{
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 muteVal;
UInt32 muteValSize = sizeof(muteVal);
OSStatus result = AudioObjectGetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
&muteValSize, &muteVal);
UInt32 muteVal;
UInt32 muteValSize = sizeof(muteVal);
OSStatus result = AudioObjectGetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
&muteValSize, &muteVal);
if (result != kAudioHardwareNoError) {
NSLog(@"No volume reported for device 0x%0x", defaultOutputDeviceID);
}
if (result != kAudioHardwareNoError) {
NSLog(@"No volume reported for device 0x%0x", defaultOutputDeviceID);
}
return muteVal;
return muteVal;
}
- (double) currentVolume
{
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioDeviceID defaultOutputDeviceID = [self getDefaultOutputDevice];
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
// First, check mute state
AudioObjectPropertyAddress mutePropertyAddress = {
kAudioDevicePropertyMute,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
Float32 volume;
UInt32 volumedataSize = sizeof(volume);
OSStatus result = AudioObjectGetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
&volumedataSize, &volume);
UInt32 muteVal = 0;
UInt32 muteValSize = sizeof(muteVal);
OSStatus muteResult = AudioObjectGetPropertyData(defaultOutputDeviceID,
&mutePropertyAddress,
0, NULL,
&muteValSize, &muteVal);
if (result != kAudioHardwareNoError) {
NSLog(@"No volume reported for device 0x%0x", defaultOutputDeviceID);
}
if (muteResult == kAudioHardwareNoError && muteVal == 1) {
return 0.0; // Treat mute as 0%
}
return ((double)volume)*100.;
// Otherwise, get the real volume
AudioObjectPropertyAddress volumePropertyAddress = {
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
Float32 volume = 0;
UInt32 volumedataSize = sizeof(volume);
OSStatus result = AudioObjectGetPropertyData(defaultOutputDeviceID,
&volumePropertyAddress,
0, NULL,
&volumedataSize, &volume);
if (result != kAudioHardwareNoError) {
NSLog(@"No volume reported for device 0x%0x", defaultOutputDeviceID);
}
return ((double)volume) * 100.0;
}
-(void)dealloc
{
}
-(id)init{
if (self = [super init]) {
[self setOldVolume:[self currentVolume]];
}
return self;
if (self = [super init]) {
[self setOldVolume:[self currentVolume]];
}
return self;
}
@end

View File

@ -19,17 +19,22 @@
"build_systems": [
{
"name": "Build with xcodebuild for debug+x86_64",
"shell_cmd": "source ./build_config.env && xcrun xcodebuild -project 'Volume Control.xcodeproj' -scheme 'Volume Control' -configuration Debug -destination 'platform=macOS,arch=x86_64' BUILD_DIR=\"\\${BUILD_DIR}\" CONFIGURATION_BUILD_DIR=\"\\${CONFIGURATION_BUILD_DIR}\" build | xcpretty",
"shell_cmd": "source ./build_config.env && xcrun xcodebuild -project 'Volume Control.xcodeproj' -scheme 'Volume Control' -configuration Debug -destination 'platform=macOS,arch=x86_64' BUILD_DIR=\"\\${BUILD_DIR}/debug\" CONFIGURATION_BUILD_DIR=\"\\${CONFIGURATION_BUILD_DIR}/debug\" build | xcpretty",
"working_dir": "${project_path}",
},
{
"name": "Distribute",
"shell_cmd": "source ./build_config.env && xcrun xcodebuild -project 'Volume Control.xcodeproj' -scheme 'Volume Control' -configuration Release ARCHS=\"arm64 x86_64\" ONLY_ACTIVE_ARCH=NO BUILD_DIR=\"\\${BUILD_DIR}/release\" CONFIGURATION_BUILD_DIR=\"\\${CONFIGURATION_BUILD_DIR}/release\" build | xcpretty",
"working_dir": "${project_path}",
},
{
"name": "Clean with xcodebuild for debug+x86_64",
"shell_cmd": "source ./build_config.env && xcrun xcodebuild -project 'Volume Control.xcodeproj' -scheme 'Volume Control' -configuration Debug -destination 'platform=macOS,arch=x86_64' BUILD_DIR=\"\\${BUILD_DIR}\" CONFIGURATION_BUILD_DIR=\"\\${CONFIGURATION_BUILD_DIR}\" clean | xcpretty",
"shell_cmd": "source ./build_config.env && xcrun xcodebuild -project 'Volume Control.xcodeproj' -scheme 'Volume Control' -configuration Debug -destination 'platform=macOS,arch=x86_64' BUILD_DIR=\"\\${BUILD_DIR}/debug\" CONFIGURATION_BUILD_DIR=\"\\${CONFIGURATION_BUILD_DIR}/debug\" clean | xcpretty",
"working_dir": "${project_path}",
},
{
"name": "Run App for debug",
"shell_cmd": "source ./build_config.env && open \"\\${CONFIGURATION_BUILD_DIR}/Volume Control.app\"",
"shell_cmd": "source ./build_config.env && open \"\\${CONFIGURATION_BUILD_DIR}/debug/Volume Control.app\"",
"working_dir": "${project_path}"
},
{

View File

@ -1,4 +1,4 @@
# build_config.env: define build variables once
export BUILD_DIR="$(dirname "$0")/build"
export CONFIGURATION_BUILD_DIR="${BUILD_DIR}/target/debug"
export CONFIGURATION_BUILD_DIR="${BUILD_DIR}/target"