Move the code for the output volume slider into a new class.

This commit is contained in:
Kyle Neideck 2017-09-16 22:09:03 +10:00
parent 3c001066c4
commit 9fd5c89b27
No known key found for this signature in database
GPG key ID: CAA8D9B8E39EC18C
6 changed files with 233 additions and 110 deletions

View file

@ -40,6 +40,9 @@
1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; };
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; };
1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };
1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };
1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };
1CACCF391F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };
1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };
1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };
@ -222,6 +225,8 @@
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = "<group>"; };
1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = "<group>"; };
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputVolumeMenuItem.h; sourceTree = "<group>"; };
1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputVolumeMenuItem.mm; sourceTree = "<group>"; };
1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMBackgroundMusicDevice.cpp; sourceTree = "<group>"; };
1CACCF381F3175AD007F86CA /* BGMBackgroundMusicDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMBackgroundMusicDevice.h; sourceTree = "<group>"; };
1CB8B3361BBA75EF000E2DD1 /* Background Music.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Background Music.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -491,6 +496,8 @@
children = (
1CB8B33B1BBA75EF000E2DD1 /* BGMAppDelegate.h */,
1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */,
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */,
1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */,
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */,
1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.mm */,
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */,
@ -914,6 +921,7 @@
2743C9F11D853FBB0089613B /* BGMUserDefaults.m in Sources */,
1C1962FD1BCAC0C3008A4DF7 /* CADebugPrintf.cpp in Sources */,
2743C9EC1D852B360089613B /* BGMScriptingBridge.m in Sources */,
1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */,
1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */,
1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */,
@ -941,6 +949,7 @@
1CD989431ECFFCFC0014BBBF /* BGMAudioDeviceManager.mm in Sources */,
1CD989441ECFFCFC0014BBBF /* BGMAutoPauseMenuItem.m in Sources */,
1CD989451ECFFCFC0014BBBF /* BGMAutoPauseMusic.mm in Sources */,
1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
1CD989461ECFFCFC0014BBBF /* BGMMusicPlayers.mm in Sources */,
1CD989471ECFFCFC0014BBBF /* BGMScriptingBridge.m in Sources */,
1CD989481ECFFCFC0014BBBF /* BGMMusicPlayer.m in Sources */,
@ -1001,6 +1010,7 @@
27FB8C071DD75D0A0084DB9D /* BGMHermes.m in Sources */,
2743CA211D86DE780089613B /* BGMDeviceControlSync.cpp in Sources */,
2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */,
1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
2743CA0D1D86D7FA0089613B /* CACFDictionary.cpp in Sources */,
2743CA0E1D86D7FA0089613B /* CACFNumber.cpp in Sources */,
2743CA0F1D86D7FA0089613B /* CACFString.cpp in Sources */,

View file

@ -39,7 +39,6 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;
@property (weak) IBOutlet NSView* outputVolumeView;
@property (weak) IBOutlet NSTextField* outputVolumeLabel;
@property (weak) IBOutlet NSSlider* outputVolumeSlider;
- (IBAction) outputVolumeSliderChanged:(NSSlider*)sender;
@property (weak) IBOutlet NSView* appVolumeView;
@property (weak) IBOutlet NSPanel* aboutPanel;
@property (unsafe_unretained) IBOutlet NSTextView* aboutPanelLicenseView;

View file

@ -32,6 +32,7 @@
#import "BGMAppVolumes.h"
#import "BGMPreferencesMenu.h"
#import "BGMXPCListener.h"
#import "BGMOutputVolumeMenuItem.h"
#import "SystemPreferences.h"
// PublicUtility Includes
@ -183,7 +184,17 @@ static float const kStatusBarIconPadding = 0.25;
[self showXPCHelperErrorMessage:error];
}];
[self initOutputDeviceVolume];
// Create the menu item with the (main) output volume slider.
BGMOutputVolumeMenuItem* outputVolume =
[[BGMOutputVolumeMenuItem alloc] initWithAudioDevices:audioDevices
view:self.outputVolumeView
slider:self.outputVolumeSlider
deviceLabel:self.outputVolumeLabel];
// Add it to the main menu below the "Volumes" heading.
[self.bgmMenu insertItem:outputVolume
atIndex:([self.bgmMenu indexOfItemWithTag:kVolumesHeadingMenuItemTag] + 1)];
appVolumes = [[BGMAppVolumes alloc] initWithMenu:self.bgmMenu
appVolumeView:self.appVolumeView
@ -199,110 +210,6 @@ static float const kStatusBarIconPadding = 0.25;
self.bgmMenu.delegate = self;
}
// TODO: Make a class for this stuff.
// TODO: Update the UI when the output device is changed.
// TODO: Disable the slider if the output device doesn't have a volume control.
// TODO: The menu (bgmMenu) should hide after you change the output volume slider, like the normal
// menu bar volume slider does.
// TODO: Move the output devices from Preferences to the main menu so they're slightly easier to
// access?
- (void) initOutputDeviceVolume {
// Put the volume slider into a menu item.
NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
menuItem.view = self.outputVolumeView;
// Add the menu item to the main menu.
NSInteger index = [self.bgmMenu indexOfItemWithTag:kVolumesHeadingMenuItemTag] + 1;
[self.bgmMenu insertItem:menuItem atIndex:index];
BGMAudioDevice bgmDevice = [audioDevices bgmDevice];
NSSlider* slider = _outputVolumeSlider;
AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;
// This block updates the value of the output volume slider.
AudioObjectPropertyListenerBlock updateSlider =
^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {
// The docs for AudioObjectPropertyListenerBlock say inAddresses will always contain
// at least one property the block is listening to, so there's no need to check
// inAddresses.
#pragma unused (inNumberAddresses, inAddresses)
try {
if (bgmDevice.GetMuteControlValue(scope, kMasterChannel)) {
// The output device is muted, so show the volume as 0 on the slider.
slider.doubleValue = 0.0;
} else {
// The slider values and volume values are both from 0 to 1, so we can use the
// volume as is.
slider.doubleValue =
bgmDevice.GetVolumeControlScalarValue(scope, kMasterChannel);
}
} catch (const CAException& e) {
NSLog(@"BGMAppDelegate::initOutputDeviceVolume: Volume slider update failed. (%d)",
e.GetError());
}
};
// Initialise the slider. (The args are ignored.)
updateSlider(0, {});
try {
// Register a listener that will update the slider when the user changes the volume from
// somewhere else.
[audioDevices bgmDevice].AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyVolumeScalar, scope),
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
updateSlider);
// Register the same listener for mute/unmute.
[audioDevices bgmDevice].AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyMute, scope),
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
updateSlider);
[self setOutputVolumeLabel];
} catch (const CAException& e) {
NSLog(@"BGMAppDelegate::initOutputDeviceVolume: Failed to init volume slider. (%d)",
e.GetError());
}
}
- (void) setOutputVolumeLabel {
BGMAudioDevice device = [audioDevices outputDevice];
AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;
UInt32 channel = kMasterChannel;
if (device.HasDataSourceControl(scope, channel)) {
UInt32 dataSourceID = device.GetCurrentDataSourceID(scope, channel);
self.outputVolumeLabel.stringValue =
(__bridge_transfer NSString*)device.CopyDataSourceNameForID(scope, channel, dataSourceID);
self.outputVolumeLabel.toolTip = (__bridge_transfer NSString*)device.CopyName();
} else {
self.outputVolumeLabel.stringValue = (__bridge_transfer NSString*)device.CopyName();
}
}
- (IBAction) outputVolumeSliderChanged:(NSSlider*)sender {
float newVolume = sender.floatValue;
AudioObjectPropertyScope scope = kAudioDevicePropertyScopeOutput;
UInt32 channel = kMasterChannel;
DebugMsg("BGMAppDelegate::outputVolumeSliderChanged: New volume: %f", newVolume);
try {
self.audioDevices.bgmDevice.SetVolumeControlScalarValue(scope, channel, newVolume);
if (self.audioDevices.bgmDevice.HasMuteControl(scope, channel)) {
self.audioDevices.bgmDevice.SetMuteControlValue(scope, channel, (newVolume < 1e-10f));
}
} catch (const CAException& e) {
NSLog(@"BGMAppDelegate::outputVolumeSliderChanged: Failed to set volume on BGMDevice. (%d)",
e.GetError());
}
}
- (BGMUserDefaults*) createUserDefaults {
BOOL persistentDefaults = [NSProcessInfo.processInfo.arguments indexOfObject:@"--no-persistent-data"] == NSNotFound;
NSUserDefaults* wrappedDefaults = persistentDefaults ? [NSUserDefaults standardUserDefaults] : nil;

View file

@ -0,0 +1,42 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMOutputVolumeMenuItem.h
// BGMApp
//
// Copyright © 2017 Kyle Neideck
//
// Local Includes
#import "BGMAudioDeviceManager.h"
// System Includes
#import <Cocoa/Cocoa.h>
@interface BGMOutputVolumeMenuItem : NSMenuItem
// A menu item with a slider for controlling the volume of the output device. Similar to the one in
// macOS's Volume menu extra.
//
// view, slider and deviceLabel are the UI elements from MainMenu.xib.
- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices
view:(NSView*)view
slider:(NSSlider*)slider
deviceLabel:(NSTextField*)label;
@end

View file

@ -0,0 +1,167 @@
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMOutputVolumeMenuItem.mm
// BGMApp
//
// Copyright © 2017 Kyle Neideck
//
// Self Include
#import "BGMOutputVolumeMenuItem.h"
// Local Includes
#import "BGMAudioDevice.h"
// PublicUtility Includes
#import "CAException.h"
#import "CAPropertyAddress.h"
// System Includes
#import <CoreAudio/AudioHardware.h>
const float SLIDER_EPSILON = 1e-10f;
const AudioObjectPropertyScope SCOPE = kAudioDevicePropertyScopeOutput;
const UInt32 CHANNEL = kMasterChannel;
@implementation BGMOutputVolumeMenuItem {
BGMAudioDeviceManager* audioDevices;
NSTextField* outputVolumeLabel;
}
// TODO: Update the UI when the output device is changed.
// TODO: Show the output device's icon next to its name.
// TODO: Disable the slider if the output device doesn't have a volume control.
// TODO: Should the menu (bgmMenu) hide after you change the output volume slider, like the normal
// menu bar volume slider does?
// TODO: Move the output devices from Preferences to the main menu so they're slightly easier to
// access?
// TODO: Update the screenshot in the README.
- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices
view:(NSView*)view
slider:(NSSlider*)slider
deviceLabel:(NSTextField*)label {
if ((self = [super initWithTitle:@"" action:nil keyEquivalent:@""])) {
audioDevices = devices;
outputVolumeLabel = label;
// Apply our custom view from MainMenu.xib.
self.view = view;
try {
[self initSlider:slider];
} catch (const CAException& e) {
NSLog(@"BGMOutputVolumeMenuItem::initWithBGMMenu: Failed to init slider. (%d)",
e.GetError());
}
}
return self;
}
- (void) initSlider:(NSSlider*)slider {
slider.target = self;
slider.action = @selector(sliderChanged:);
BGMAudioDevice bgmDevice = [audioDevices bgmDevice];
// This block updates the value of the output volume slider.
AudioObjectPropertyListenerBlock updateSlider =
^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress* inAddresses) {
// The docs for AudioObjectPropertyListenerBlock say inAddresses will always contain
// at least one property the block is listening to, so there's no need to check
// inAddresses.
#pragma unused (inNumberAddresses, inAddresses)
try {
if (bgmDevice.GetMuteControlValue(SCOPE, kMasterChannel)) {
// The output device is muted, so show the volume as 0 on the slider.
slider.doubleValue = 0.0;
} else {
// The slider values and volume values are both from 0 to 1, so we can use the
// volume as is.
slider.doubleValue =
bgmDevice.GetVolumeControlScalarValue(SCOPE, kMasterChannel);
}
} catch (const CAException& e) {
NSLog(@"BGMOutputVolumeMenuItem::initSlider: Failed to update slider. (%d)",
e.GetError());
}
};
// Initialise the slider. (The args are ignored.)
updateSlider(0, {});
// Register a listener that will update the slider when the user changes the volume from
// somewhere else.
audioDevices.bgmDevice.AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyVolumeScalar, SCOPE),
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
updateSlider);
// Register the same listener for mute/unmute.
audioDevices.bgmDevice.AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyMute, SCOPE),
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
updateSlider);
[self setOutputVolumeLabel];
}
// Sets the label to the name of the output device.
- (void) setOutputVolumeLabel {
BGMAudioDevice device = audioDevices.outputDevice;
if (device.HasDataSourceControl(SCOPE, CHANNEL)) {
UInt32 dataSourceID = device.GetCurrentDataSourceID(SCOPE, CHANNEL);
outputVolumeLabel.stringValue =
(__bridge_transfer NSString*)device.CopyDataSourceNameForID(SCOPE,
CHANNEL,
dataSourceID);
outputVolumeLabel.toolTip = (__bridge_transfer NSString*)device.CopyName();
} else {
outputVolumeLabel.stringValue = (__bridge_transfer NSString*)device.CopyName();
}
}
// Called when the user slides the slider.
- (IBAction) sliderChanged:(NSSlider*)sender {
float newValue = sender.floatValue;
DebugMsg("BGMOutputVolumeMenuItem::sliderChanged: New value: %f", newValue);
// Update BGMDevice's volume to the new value selected by the user.
try {
// The slider values and volume values are both from 0.0f to 1.0f, so we can use the slider
// value as is.
audioDevices.bgmDevice.SetVolumeControlScalarValue(SCOPE, CHANNEL, newValue);
// Mute BGMDevice if they set the slider to zero, and unmute it for non-zero. Muting makes
// sure the audio doesn't play very quietly instead being completely silent. This matches
// the behaviour of the Volume menu built-in to macOS.
if (audioDevices.bgmDevice.HasMuteControl(SCOPE, CHANNEL)) {
audioDevices.bgmDevice.SetMuteControlValue(SCOPE, CHANNEL, (newValue < SLIDER_EPSILON));
}
} catch (const CAException& e) {
NSLog(@"BGMOutputVolumeMenuItem::sliderChanged: Failed to set volume (%d)", e.GetError());
}
}
@end

View file

@ -30,7 +30,7 @@
<items>
<menuItem title="Auto-pause Music" tag="2" id="nHv-T8-1nb">
<modifierMask key="keyEquivalentModifierMask"/>
<accessibility description="Enable to automatically pause your selected music player when a different app starts playing audio." identifier="Auto-pause enabled"/>
<accessibility description="Toggle Auto-pause" help="Enable to automatically pause your selected music player when a different app starts playing audio." identifier="Auto-pause enabled"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="ZGd-Pq-YeA"/>
<menuItem title="Volumes" tag="3" enabled="NO" id="8PP-wA-Pae">
@ -271,9 +271,7 @@
<rect key="frame" x="20" y="4" width="220" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" tickMarkPosition="above" sliderType="linear" id="MzM-fe-nKb"/>
<connections>
<action selector="outputVolumeSliderChanged:" target="Voe-Tx-rLC" id="Cyt-IC-fXE"/>
</connections>
<accessibility description="Output Volume" help="Sets the volume of your audio output device."/>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wfC-C6-SLv">
<rect key="frame" x="20" y="25" width="226" height="17"/>