Skip to content

Commit

Permalink
Add an option to use a volume icon instead of the Background Music logo.
Browse files Browse the repository at this point in the history
This is so the icon can show the current volume. Then you can hide the
built-in volume status bar item in System Preferences.

Closes #183.
  • Loading branch information
kyleneideck committed Mar 4, 2019
1 parent 14df80d commit e093e7d
Show file tree
Hide file tree
Showing 24 changed files with 776 additions and 144 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ tags
cmake-build-debug/
/Background-Music-*/
BGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
Images/*.aux
Images/*.log

# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore

Expand Down
20 changes: 20 additions & 0 deletions BGMApp/BGMApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
objects = {

/* Begin PBXBuildFile section */
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAutoPauseMusicPrefs.mm"; }; };
1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMPreferencesMenu.mm"; }; };
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAutoPauseMusic.mm"; }; };
Expand Down Expand Up @@ -206,6 +212,10 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMVolumeChangeListener.cpp; sourceTree = "<group>"; };
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMStatusBarItem.mm; sourceTree = "<group>"; };
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMStatusBarItem.h; sourceTree = "<group>"; };
19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMVolumeChangeListener.h; sourceTree = "<group>"; };
1C0BD0A31BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAutoPauseMusicPrefs.h; path = Preferences/BGMAutoPauseMusicPrefs.h; sourceTree = "<group>"; };
1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAutoPauseMusicPrefs.mm; path = Preferences/BGMAutoPauseMusicPrefs.mm; sourceTree = "<group>"; };
1C0BD0A61BF1B029004F4CF5 /* BGMPreferencesMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMPreferencesMenu.h; path = Preferences/BGMPreferencesMenu.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -579,10 +589,14 @@
1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */,
1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,
1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */,
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */,
1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,
1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */,
2743C9ED1D8538700089613B /* BGMUserDefaults.h */,
2743C9F01D853FBB0089613B /* BGMUserDefaults.m */,
19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */,
19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */,
2795973C1C982E8C00A002FB /* BGMXPCListener.h */,
2795973A1C982E4E00A002FB /* BGMXPCListener.mm */,
1C2FC3161EC7078F00A76592 /* Scripting */,
Expand Down Expand Up @@ -1004,6 +1018,8 @@
2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */,
27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */,
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */,
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */,
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1061,6 +1077,8 @@
1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */,
1C2FC31C1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */,
1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */,
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1134,6 +1152,8 @@
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,
2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */,
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
105 changes: 24 additions & 81 deletions BGMApp/BGMApp/BGMAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
// BGMAppDelegate.mm
// BGMApp
//
// Copyright © 2016-2018 Kyle Neideck
// Copyright © 2016-2019 Kyle Neideck
//

// Self Includes
// Self Include
#import "BGMAppDelegate.h"

// Local Includes
Expand All @@ -33,6 +33,7 @@
#import "BGMOutputVolumeMenuItem.h"
#import "BGMPreferencesMenu.h"
#import "BGMPreferredOutputDevices.h"
#import "BGMStatusBarItem.h"
#import "BGMSystemSoundsVolume.h"
#import "BGMTermination.h"
#import "BGMUserDefaults.h"
Expand All @@ -45,18 +46,19 @@

#pragma clang assume_nonnull begin

static float const kStatusBarIconPadding = 0.25;
static NSString* const kOptNoPersistentData = @"--no-persistent-data";
static NSString* const kOptShowDockIcon = @"--show-dock-icon";

@implementation BGMAppDelegate {
// The button in the system status bar (the bar with volume, battery, clock, etc.) to show the main menu
// for the app. These are called "menu bar extras" in the Human Interface Guidelines.
NSStatusItem* statusBarItem;
// The button in the system status bar that shows the main menu.
BGMStatusBarItem* statusBarItem;

// Only show the 'BGMXPCHelper is missing' error dialog once.
BOOL haveShownXPCHelperErrorMessage;


// Persistently stores user settings and data.
BGMUserDefaults* userDefaults;

BGMAutoPauseMusic* autoPauseMusic;
BGMAutoPauseMenuItem* autoPauseMenuItem;
BGMMusicPlayers* musicPlayers;
Expand All @@ -71,6 +73,8 @@ @implementation BGMAppDelegate {
@synthesize audioDevices = audioDevices;

- (void) awakeFromNib {
[super awakeFromNib];

// Show BGMApp in the dock, if the command-line option for that was passed. This is used by the
// UI tests.
if ([NSProcessInfo.processInfo.arguments indexOfObject:kOptShowDockIcon] != NSNotFound) {
Expand All @@ -79,72 +83,19 @@ - (void) awakeFromNib {

haveShownXPCHelperErrorMessage = NO;

[self initStatusBarItem];
}

// Set up the status bar item. (The thing you click to show BGMApp's UI.)
- (void) initStatusBarItem {
statusBarItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];

// NSStatusItem doesn't have the "button" property on OS X 10.9.
BOOL buttonAvailable = (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10);

// Set the title/tooltip to "Background Music".
statusBarItem.title = [NSRunningApplication currentApplication].localizedName;
statusBarItem.toolTip = statusBarItem.title;

if (buttonAvailable) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
statusBarItem.button.accessibilityLabel = statusBarItem.title;
#pragma clang diagnostic pop
// Set up audioDevices, which coordinates BGMDevice and the output device. It manages
// playthrough, volume/mute controls, etc.
if (![self initAudioDeviceManager]) {
return;
}

// Set the icon.
NSImage* icon = [NSImage imageNamed:@"FermataIcon"];

if (icon != nil) {
NSRect statusBarItemFrame;

if (buttonAvailable) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
statusBarItemFrame = statusBarItem.button.frame;
#pragma clang diagnostic pop
} else {
// OS X 10.9 fallback. I haven't tested this (or anything else on 10.9).
statusBarItemFrame = statusBarItem.view.frame;
}

CGFloat lengthMinusPadding = statusBarItemFrame.size.height * (1 - kStatusBarIconPadding);
[icon setSize:NSMakeSize(lengthMinusPadding, lengthMinusPadding)];
// Stored user settings
userDefaults = [self createUserDefaults];

// Make the icon a "template image" so it gets drawn colour-inverted when it's highlighted or the status
// bar's in dark mode
[icon setTemplate:YES];

if (buttonAvailable) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
statusBarItem.button.image = icon;
#pragma clang diagnostic pop
} else {
statusBarItem.image = icon;
}
} else {
// If our icon is missing for some reason, fallback to a fermata character (1D110)
if (buttonAvailable) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
statusBarItem.button.title = @"𝄐";
#pragma clang diagnostic pop
} else {
statusBarItem.title = @"𝄐";
}
}

// Set the main menu
statusBarItem.menu = self.bgmMenu;
// Add the status bar item. (The thing you click to show BGMApp's main menu.)
statusBarItem = [[BGMStatusBarItem alloc] initWithMenu:self.bgmMenu
audioDevices:audioDevices
userDefaults:userDefaults];
}

- (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
Expand All @@ -159,15 +110,6 @@ - (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"],
NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]);

// Set up audioDevices, which coordinates BGMDevice and the output device. It manages
// playthrough, volume/mute controls, etc.
if (![self initAudioDeviceManager]) {
return;
}

// Persistently stores user settings and data.
BGMUserDefaults* userDefaults = [self createUserDefaults];

// Handles changing (or not changing) the output device when devices are added or removed. Must
// be initialised before calling setBGMDeviceAsDefault.
preferredOutputDevices =
Expand All @@ -191,7 +133,7 @@ - (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
musicPlayers:musicPlayers];

[self setUpMainMenu:userDefaults];
[self setUpMainMenu];

xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
helperConnectionErrorHandler:^(NSError* error) {
Expand Down Expand Up @@ -285,7 +227,7 @@ - (void) setBGMDeviceAsDefault {
}
}

- (void) setUpMainMenu:(BGMUserDefaults*)userDefaults {
- (void) setUpMainMenu {
autoPauseMenuItem =
[[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped
autoPauseMusic:autoPauseMusic
Expand All @@ -305,6 +247,7 @@ - (void) setUpMainMenu:(BGMUserDefaults*)userDefaults {
prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu
audioDevices:audioDevices
musicPlayers:musicPlayers
statusBarItem:statusBarItem
aboutPanel:self.aboutPanel
aboutPanelLicenseView:self.aboutPanelLicenseView];

Expand Down
2 changes: 1 addition & 1 deletion BGMApp/BGMApp/BGMOutputDeviceMenuSection.mm
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ - (void) insertMenuItemsForDevice:(BGMAudioDevice)device {
NSMutableArray<NSMenuItem*>* items = [NSMutableArray new];

AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;
UInt32 channel = 0; // 0 is the master channel.
UInt32 channel = kAudioObjectPropertyElementMaster;

// If the device has data sources, create a menu item for each. Otherwise, create a single menu item
// for the device. This way the menu items' titles will be, for example, "Internal Speakers" rather
Expand Down
55 changes: 9 additions & 46 deletions BGMApp/BGMApp/BGMOutputVolumeMenuItem.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// BGMOutputVolumeMenuItem.mm
// BGMApp
//
// Copyright © 2017, 2018 Kyle Neideck
// Copyright © 2017-2019 Kyle Neideck
//

// Self Include
Expand All @@ -26,6 +26,7 @@
// Local Includes
#import "BGM_Utils.h"
#import "BGMAudioDevice.h"
#import "BGMVolumeChangeListener.h"

// PublicUtility Includes
#import "CAException.h"
Expand All @@ -46,7 +47,7 @@ @implementation BGMOutputVolumeMenuItem {
NSTextField* deviceLabel;
NSSlider* volumeSlider;
BGMAudioDevice outputDevice;
AudioObjectPropertyListenerBlock updateSliderListenerBlock;
BGMVolumeChangeListener* volumeChangeListener;
AudioObjectPropertyListenerBlock updateLabelListenerBlock;
}

Expand All @@ -66,9 +67,8 @@ - (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices
volumeSlider = slider;
outputDevice = audioDevices.outputDevice;

// These are initialised in the methods called below.
updateSliderListenerBlock = nil;
updateLabelListenerBlock = nil;
// volumeChangeListener and updateLabelListenerBlock are initialised in the methods called
// below.

// Apply our custom view from MainMenu.xib.
self.view = view;
Expand All @@ -89,20 +89,6 @@ - (void) dealloc {
// TODO: This call isn't thread safe. (But currently this dealloc method is only called if
// there's an error.)
[self removeOutputDeviceDataSourceListener];

BGMLogAndSwallowExceptions("BGMOutputVolumeMenuItem::dealloc", ([&] {
audioDevices.bgmDevice.RemovePropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyVolumeScalar, kScope),
dispatch_get_main_queue(),
updateSliderListenerBlock);
}));

BGMLogAndSwallowExceptions("BGMOutputVolumeMenuItem::dealloc", ([&] {
audioDevices.bgmDevice.RemovePropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyMute, kScope),
dispatch_get_main_queue(),
updateSliderListenerBlock);
}));
}

- (void) initSlider {
Expand All @@ -118,32 +104,9 @@ - (void) initSlider {
// Register a listener that will update the slider when the user changes the volume or
// mutes/unmutes their audio.
BGMOutputVolumeMenuItem* __weak weakSelf = self;

updateSliderListenerBlock =
^(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 it.
#pragma unused (inNumberAddresses, inAddresses)
[weakSelf updateVolumeSlider];
};

// Instead of swallowing exceptions, we could try again later, but I doubt it would be worth the
// effort. And the documentation doesn't actually explain what could cause this to fail.
BGMLogAndSwallowExceptions("BGMOutputVolumeMenuItem::initSlider", ([&] {
// Register the listener to receive volume notifications.
audioDevices.bgmDevice.AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyVolumeScalar, kScope),
dispatch_get_main_queue(),
updateSliderListenerBlock);
}));

BGMLogAndSwallowExceptions("BGMOutputVolumeMenuItem::initSlider", ([&] {
// Register the same listener for mute/unmute notifications.
audioDevices.bgmDevice.AddPropertyListenerBlock(
CAPropertyAddress(kAudioDevicePropertyMute, kScope),
dispatch_get_main_queue(),
updateSliderListenerBlock);
}));
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
[weakSelf updateVolumeSlider];
});
}

// Updates the value of the output volume slider. Should only be called on the main thread because
Expand Down Expand Up @@ -178,7 +141,7 @@ - (void) updateVolumeSlider {
volumeSlider.doubleValue = 0.0;
}
}));
};
}

- (void) addOutputDeviceDataSourceListener {
// Create the block that updates deviceLabel when the output device's data source changes, e.g.
Expand Down
Loading

0 comments on commit e093e7d

Please sign in to comment.