mirror of
https://github.com/kyleneideck/BackgroundMusic
synced 2024-11-22 12:13:03 +00:00
Add an option to use a volume icon instead of the Background Music logo.
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.
This commit is contained in:
parent
14df80da24
commit
e093e7d3b2
24 changed files with 776 additions and 144 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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"; }; };
|
||||
|
@ -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>"; };
|
||||
|
@ -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 */,
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -71,6 +73,8 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
@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) {
|
||||
|
@ -79,72 +83,19 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
|
||||
haveShownXPCHelperErrorMessage = NO;
|
||||
|
||||
[self initStatusBarItem];
|
||||
// Set up audioDevices, which coordinates BGMDevice and the output device. It manages
|
||||
// playthrough, volume/mute controls, etc.
|
||||
if (![self initAudioDeviceManager]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the status bar item. (The thing you click to show BGMApp's UI.)
|
||||
- (void) initStatusBarItem {
|
||||
statusBarItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
|
||||
// Stored user settings
|
||||
userDefaults = [self createUserDefaults];
|
||||
|
||||
// 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 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)];
|
||||
|
||||
// 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 {
|
||||
|
@ -159,15 +110,6 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
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 =
|
||||
|
@ -191,7 +133,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
|
||||
musicPlayers:musicPlayers];
|
||||
|
||||
[self setUpMainMenu:userDefaults];
|
||||
[self setUpMainMenu];
|
||||
|
||||
xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
|
||||
helperConnectionErrorHandler:^(NSError* error) {
|
||||
|
@ -285,7 +227,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
}
|
||||
}
|
||||
|
||||
- (void) setUpMainMenu:(BGMUserDefaults*)userDefaults {
|
||||
- (void) setUpMainMenu {
|
||||
autoPauseMenuItem =
|
||||
[[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped
|
||||
autoPauseMusic:autoPauseMusic
|
||||
|
@ -305,6 +247,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
|||
prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu
|
||||
audioDevices:audioDevices
|
||||
musicPlayers:musicPlayers
|
||||
statusBarItem:statusBarItem
|
||||
aboutPanel:self.aboutPanel
|
||||
aboutPanelLicenseView:self.aboutPanelLicenseView];
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ static NSInteger const kOutputDeviceMenuItemTag = 5;
|
|||
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
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
// BGMOutputVolumeMenuItem.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017, 2018 Kyle Neideck
|
||||
// Copyright © 2017-2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -26,6 +26,7 @@
|
|||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMAudioDevice.h"
|
||||
#import "BGMVolumeChangeListener.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CAException.h"
|
||||
|
@ -46,7 +47,7 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
|||
NSTextField* deviceLabel;
|
||||
NSSlider* volumeSlider;
|
||||
BGMAudioDevice outputDevice;
|
||||
AudioObjectPropertyListenerBlock updateSliderListenerBlock;
|
||||
BGMVolumeChangeListener* volumeChangeListener;
|
||||
AudioObjectPropertyListenerBlock updateLabelListenerBlock;
|
||||
}
|
||||
|
||||
|
@ -66,9 +67,8 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
|||
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;
|
||||
|
@ -89,20 +89,6 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
|||
// 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 {
|
||||
|
@ -118,32 +104,9 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
|||
// 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)
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
|
||||
[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);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Updates the value of the output volume slider. Should only be called on the main thread because
|
||||
|
@ -178,7 +141,7 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
|||
volumeSlider.doubleValue = 0.0;
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
- (void) addOutputDeviceDataSourceListener {
|
||||
// Create the block that updates deviceLabel when the output device's data source changes, e.g.
|
||||
|
|
63
BGMApp/BGMApp/BGMStatusBarItem.h
Normal file
63
BGMApp/BGMApp/BGMStatusBarItem.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMStatusBarItem.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
// Forward Declarations
|
||||
@class BGMUserDefaults;
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
typedef NS_ENUM(NSInteger, BGMStatusBarIcon) {
|
||||
BGMFermataStatusBarIcon = 0,
|
||||
BGMVolumeStatusBarIcon
|
||||
};
|
||||
|
||||
static BGMStatusBarIcon const kBGMStatusBarIconMinValue = BGMFermataStatusBarIcon;
|
||||
static BGMStatusBarIcon const kBGMStatusBarIconMaxValue = BGMVolumeStatusBarIcon;
|
||||
static BGMStatusBarIcon const kBGMStatusBarIconDefaultValue = BGMFermataStatusBarIcon;
|
||||
|
||||
@interface BGMStatusBarItem : NSObject
|
||||
|
||||
- (instancetype) initWithMenu:(NSMenu*)bgmMenu
|
||||
audioDevices:(BGMAudioDeviceManager*)devices
|
||||
userDefaults:(BGMUserDefaults*)defaults;
|
||||
|
||||
// Set this to BGMFermataStatusBarIcon to change the icon to the Background Music logo.
|
||||
//
|
||||
// Set this to BGMFermataStatusBarIcon to change the icon to a volume icon. This icon has the
|
||||
// advantage of indicating the volume level, but we can't make it the default because it looks the
|
||||
// same as the icon for the macOS volume status bar item.
|
||||
@property BGMStatusBarIcon icon;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
257
BGMApp/BGMApp/BGMStatusBarItem.mm
Normal file
257
BGMApp/BGMApp/BGMStatusBarItem.mm
Normal file
|
@ -0,0 +1,257 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMStatusBarItem.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMStatusBarItem.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMUserDefaults.h"
|
||||
#import "BGMVolumeChangeListener.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
static CGFloat const kStatusBarIconPadding = 0.25;
|
||||
static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
|
||||
@implementation BGMStatusBarItem
|
||||
{
|
||||
BGMAudioDeviceManager* audioDevices;
|
||||
|
||||
// User settings and data.
|
||||
BGMUserDefaults* userDefaults;
|
||||
|
||||
NSImage* fermataIcon;
|
||||
NSImage* volumeIcon0SoundWaves;
|
||||
NSImage* volumeIcon1SoundWave;
|
||||
NSImage* volumeIcon2SoundWaves;
|
||||
NSImage* volumeIcon3SoundWaves;
|
||||
|
||||
NSStatusItem* statusBarItem;
|
||||
BGMVolumeChangeListener* volumeChangeListener;
|
||||
|
||||
BGMStatusBarIcon _icon;
|
||||
}
|
||||
|
||||
- (instancetype) initWithMenu:(NSMenu*)bgmMenu
|
||||
audioDevices:(BGMAudioDeviceManager*)devices
|
||||
userDefaults:(BGMUserDefaults*)defaults {
|
||||
if ((self = [super init])) {
|
||||
statusBarItem =
|
||||
[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
|
||||
|
||||
audioDevices = devices;
|
||||
userDefaults = defaults;
|
||||
|
||||
// Initialise the icons.
|
||||
[self initIcons];
|
||||
|
||||
// Set the initial icon.
|
||||
self.icon = userDefaults.statusBarIcon;
|
||||
|
||||
// Set the menu item to open the main menu.
|
||||
statusBarItem.menu = bgmMenu;
|
||||
|
||||
// Set the accessibility label to "Background Music". (We intentionally don't set a title or
|
||||
// a tooltip.)
|
||||
if ([BGMStatusBarItem buttonAvailable]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
statusBarItem.button.accessibilityLabel =
|
||||
[NSRunningApplication currentApplication].localizedName;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
// Update the icon when BGMDevice's volume changes.
|
||||
BGMStatusBarItem* __weak weakSelf = self;
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
|
||||
[weakSelf bgmDeviceVolumeDidChange];
|
||||
});
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
delete volumeChangeListener;
|
||||
}
|
||||
|
||||
- (void) initIcons {
|
||||
// Load the icons.
|
||||
fermataIcon = [NSImage imageNamed:@"FermataIcon"];
|
||||
volumeIcon0SoundWaves = [NSImage imageNamed:@"Volume0"];
|
||||
volumeIcon1SoundWave = [NSImage imageNamed:@"Volume1"];
|
||||
volumeIcon2SoundWaves = [NSImage imageNamed:@"Volume2"];
|
||||
volumeIcon3SoundWaves = [NSImage imageNamed:@"Volume3"];
|
||||
|
||||
// Set the icons' sizes.
|
||||
NSRect statusBarItemFrame;
|
||||
|
||||
if ([BGMStatusBarItem 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 heightMinusPadding = statusBarItemFrame.size.height * (1 - kStatusBarIconPadding);
|
||||
|
||||
// The fermata icon has equal width and height.
|
||||
[fermataIcon setSize:NSMakeSize(heightMinusPadding, heightMinusPadding)];
|
||||
|
||||
// The volume icons are all the same width and height.
|
||||
CGFloat volumeIconWidthToHeightRatio =
|
||||
volumeIcon0SoundWaves.size.width / volumeIcon0SoundWaves.size.height;
|
||||
CGFloat volumeIconWidth = heightMinusPadding * volumeIconWidthToHeightRatio;
|
||||
CGFloat volumeIconHeight = heightMinusPadding * (1 - kVolumeIconAdditionalVerticalPadding);
|
||||
|
||||
[volumeIcon0SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];
|
||||
[volumeIcon1SoundWave setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];
|
||||
[volumeIcon2SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];
|
||||
[volumeIcon3SoundWaves setSize:NSMakeSize(volumeIconWidth, volumeIconHeight)];
|
||||
|
||||
// Make the icons "template images" so they get drawn colour-inverted when they're highlighted
|
||||
// or the system is in dark mode.
|
||||
[fermataIcon setTemplate:YES];
|
||||
[volumeIcon0SoundWaves setTemplate:YES];
|
||||
[volumeIcon1SoundWave setTemplate:YES];
|
||||
[volumeIcon2SoundWaves setTemplate:YES];
|
||||
[volumeIcon3SoundWaves setTemplate:YES];
|
||||
}
|
||||
|
||||
+ (BOOL) buttonAvailable {
|
||||
// NSStatusItem doesn't have the "button" property on OS X 10.9.
|
||||
return (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10);
|
||||
}
|
||||
|
||||
- (void) setImage:(NSImage*)image {
|
||||
if ([BGMStatusBarItem buttonAvailable]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
statusBarItem.button.image = image;
|
||||
#pragma clang diagnostic pop
|
||||
} else {
|
||||
statusBarItem.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
- (BGMStatusBarIcon) icon {
|
||||
return _icon;
|
||||
}
|
||||
|
||||
- (void) setIcon:(BGMStatusBarIcon)icon {
|
||||
_icon = icon;
|
||||
|
||||
// Save the setting.
|
||||
userDefaults.statusBarIcon = self.icon;
|
||||
|
||||
// Change the icon (i.e. the image). Dispatch this to the main thread because it changes the UI.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (_icon == BGMFermataStatusBarIcon) {
|
||||
[self setImage:fermataIcon];
|
||||
|
||||
// If the icon was greyed out, change it back.
|
||||
if ([BGMStatusBarItem buttonAvailable]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
statusBarItem.button.appearsDisabled = NO;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
} else {
|
||||
BGMAssert((_icon == BGMVolumeStatusBarIcon), "Unknown icon in enum");
|
||||
|
||||
[self updateVolumeStatusBarIcon];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void) bgmDeviceVolumeDidChange {
|
||||
if (self.icon == BGMVolumeStatusBarIcon) {
|
||||
[self updateVolumeStatusBarIcon];
|
||||
}
|
||||
}
|
||||
|
||||
// Should only be called on the main thread because it calls UI functions.
|
||||
- (void) updateVolumeStatusBarIcon {
|
||||
BGMAssert([[NSThread currentThread] isMainThread],
|
||||
"updateVolumeStatusBarIcon called on non-main thread.");
|
||||
BGMAssert((self.icon == BGMVolumeStatusBarIcon), "Volume status bar icon not enabled");
|
||||
|
||||
BGMAudioDevice bgmDevice = [audioDevices bgmDevice];
|
||||
|
||||
// BGMDevice should never return an error for these calls, so we just swallow any exceptions and
|
||||
// give up.
|
||||
BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
|
||||
AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;
|
||||
AudioObjectPropertyScope element = kAudioObjectPropertyElementMaster;
|
||||
|
||||
BOOL hasVolume = bgmDevice.HasVolumeControl(scope, element);
|
||||
|
||||
// Show the button as greyed out if BGMDevice doesn't have a volume control (which means the
|
||||
// output device doesn't have one).
|
||||
if ([BGMStatusBarItem buttonAvailable]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
statusBarItem.button.appearsDisabled = !hasVolume;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
if (hasVolume) {
|
||||
if (bgmDevice.HasMuteControl(scope, element) &&
|
||||
bgmDevice.GetMuteControlValue(scope, element)) {
|
||||
// The device is muted, so use the zero waves icon.
|
||||
[self setImage:volumeIcon0SoundWaves];
|
||||
} else {
|
||||
// Set the icon to reflect the device's volume.
|
||||
double volume = bgmDevice.GetVolumeControlScalarValue(scope, element);
|
||||
|
||||
// These values match the macOS volume status bar item, except for the first one. I
|
||||
// don't know why, but at a very low volume macOS will show the zero waves icon even
|
||||
// though the sound is still audible.
|
||||
if (volume == 0.05) {
|
||||
[self setImage:volumeIcon0SoundWaves];
|
||||
} else if (volume < 0.33) {
|
||||
[self setImage:volumeIcon1SoundWave];
|
||||
} else if (volume < 0.66) {
|
||||
[self setImage:volumeIcon2SoundWaves];
|
||||
} else {
|
||||
[self setImage:volumeIcon3SoundWaves];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Always use the full-volume icon when the device has no volume control.
|
||||
[self setImage:volumeIcon3SoundWaves];
|
||||
}
|
||||
});
|
||||
|
||||
DebugMsg("BGMStatusBarItem::updateVolumeStatusBarIcon: Set icon to %s",
|
||||
statusBarItem.image.name.UTF8String);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
|
@ -17,13 +17,16 @@
|
|||
// BGMUserDefaults.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
//
|
||||
// A simple wrapper around our use of NSUserDefaults. Used to store the preferences/state that only
|
||||
// apply to BGMApp. The others are stored by BGMDriver.
|
||||
//
|
||||
|
||||
// System includes
|
||||
// Local Includes
|
||||
#import "BGMStatusBarItem.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
|
@ -44,6 +47,10 @@
|
|||
// device is at index 0. See BGMPreferredOutputDevices.
|
||||
@property NSArray<NSString*>* preferredDeviceUIDs;
|
||||
|
||||
// The (type of) icon to show in the button in the status bar. (The button the user clicks to open
|
||||
// BGMApp's main menu.)
|
||||
@property BGMStatusBarIcon statusBarIcon;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
// BGMUserDefaults.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -33,6 +33,7 @@
|
|||
static NSString* const BGMDefaults_AutoPauseMusicEnabled = @"AutoPauseMusicEnabled";
|
||||
static NSString* const BGMDefaults_SelectedMusicPlayerID = @"SelectedMusicPlayerID";
|
||||
static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||
static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||
|
||||
@implementation BGMUserDefaults {
|
||||
// The defaults object wrapped by this object.
|
||||
|
@ -89,6 +90,22 @@ static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
|||
[self set:BGMDefaults_PreferredDeviceUIDs to:devices];
|
||||
}
|
||||
|
||||
- (BGMStatusBarIcon) statusBarIcon {
|
||||
NSInteger icon = [self getInt:BGMDefaults_StatusBarIcon or:kBGMStatusBarIconDefaultValue];
|
||||
|
||||
// Just in case we get an invalid value somehow.
|
||||
if ((icon < kBGMStatusBarIconMinValue) || (icon > kBGMStatusBarIconMaxValue)) {
|
||||
NSLog(@"BGMUserDefaults::statusBarIcon: Unknown BGMStatusBarIcon: %ld", (long)icon);
|
||||
icon = kBGMStatusBarIconDefaultValue;
|
||||
}
|
||||
|
||||
return (BGMStatusBarIcon)icon;
|
||||
}
|
||||
|
||||
- (void) setStatusBarIcon:(BGMStatusBarIcon)icon {
|
||||
[self setInt:BGMDefaults_StatusBarIcon to:icon];
|
||||
}
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
- (id __nullable) get:(NSString*)key {
|
||||
|
@ -103,6 +120,7 @@ static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: This method should have a default value param.
|
||||
- (BOOL) getBool:(NSString*)key {
|
||||
return defaults ? [defaults boolForKey:key] : [transientDefaults[key] boolValue];
|
||||
}
|
||||
|
@ -111,7 +129,32 @@ static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
|||
if (defaults) {
|
||||
[defaults setBool:value forKey:key];
|
||||
} else {
|
||||
transientDefaults[key] = [NSNumber numberWithBool:value];
|
||||
transientDefaults[key] = @(value);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) getInt:(NSString*)key or:(NSInteger)valueIfNil
|
||||
{
|
||||
if (defaults) {
|
||||
if ([defaults objectForKey:key]) {
|
||||
return [defaults integerForKey:key];
|
||||
} else {
|
||||
return valueIfNil;
|
||||
}
|
||||
} else {
|
||||
if (transientDefaults[key]) {
|
||||
return [transientDefaults[key] intValue];
|
||||
} else {
|
||||
return valueIfNil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setInt:(NSString*)key to:(NSInteger)value {
|
||||
if (defaults) {
|
||||
[defaults setInteger:value forKey:key];
|
||||
} else {
|
||||
transientDefaults[key] = @(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
100
BGMApp/BGMApp/BGMVolumeChangeListener.cpp
Normal file
100
BGMApp/BGMApp/BGMVolumeChangeListener.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMVolumeChangeListener.cpp
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGMVolumeChangeListener.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMAudioDevice.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CAException.h"
|
||||
#import "CAPropertyAddress.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wexit-time-destructors"
|
||||
const static std::vector<CAPropertyAddress> kVolumeChangeProperties = {
|
||||
// Output volume changes
|
||||
CAPropertyAddress(kAudioDevicePropertyVolumeScalar, kAudioObjectPropertyScopeOutput),
|
||||
// Mute/unmute
|
||||
CAPropertyAddress(kAudioDevicePropertyMute, kAudioObjectPropertyScopeOutput),
|
||||
// Received when controls are added to or removed from the device.
|
||||
CAPropertyAddress(kAudioObjectPropertyControlList),
|
||||
// Received when the device has changed and "clients should re-evaluate everything they need
|
||||
// to know about the device, particularly the layout and values of the controls".
|
||||
CAPropertyAddress(kAudioDevicePropertyDeviceHasChanged)
|
||||
};
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
BGMVolumeChangeListener::BGMVolumeChangeListener(BGMAudioDevice device,
|
||||
std::function<void(void)> handler)
|
||||
:
|
||||
mDevice(device)
|
||||
{
|
||||
// Register a listener that will update the slider when the user changes the volume or
|
||||
// mutes/unmutes their audio.
|
||||
mListenerBlock =
|
||||
Block_copy(^(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.
|
||||
(void)inNumberAddresses;
|
||||
(void)inAddresses;
|
||||
|
||||
// Call the callback.
|
||||
handler();
|
||||
});
|
||||
|
||||
// Register for a number of properties that might indicate that clients need to update. For
|
||||
// example, the mute property changing means UI elements that display the volume will need to be
|
||||
// updated, even though it's not strictly a change in volume.
|
||||
for(CAPropertyAddress property : kVolumeChangeProperties)
|
||||
{
|
||||
// Instead of swallowing exceptions here, 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
|
||||
// call to fail.
|
||||
BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
|
||||
mDevice.AddPropertyListenerBlock(property, dispatch_get_main_queue(), mListenerBlock);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BGMVolumeChangeListener::~BGMVolumeChangeListener()
|
||||
{
|
||||
// Deregister and release the listener block.
|
||||
for(CAPropertyAddress property : kVolumeChangeProperties)
|
||||
{
|
||||
BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
|
||||
mDevice.RemovePropertyListenerBlock(property,
|
||||
dispatch_get_main_queue(),
|
||||
mListenerBlock);
|
||||
});
|
||||
}
|
||||
|
||||
Block_release(mListenerBlock);
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
59
BGMApp/BGMApp/BGMVolumeChangeListener.h
Normal file
59
BGMApp/BGMApp/BGMVolumeChangeListener.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMVolumeChangeListener.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#include "BGMBackgroundMusicDevice.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CAPropertyAddress.h"
|
||||
|
||||
// STL Includes
|
||||
#include <functional>
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGMVolumeChangeListener
|
||||
{
|
||||
|
||||
public:
|
||||
/*!
|
||||
* @param device Listens for notifications about this device.
|
||||
* @param handler The function to call when the device's volume (or mute) changes. Called on the
|
||||
* main queue.
|
||||
*/
|
||||
BGMVolumeChangeListener(BGMAudioDevice device, std::function<void(void)> handler);
|
||||
virtual ~BGMVolumeChangeListener();
|
||||
BGMVolumeChangeListener(const BGMVolumeChangeListener&) = delete;
|
||||
BGMVolumeChangeListener& operator=(const BGMVolumeChangeListener&) = delete;
|
||||
|
||||
private:
|
||||
AudioObjectPropertyListenerBlock mListenerBlock;
|
||||
BGMAudioDevice mDevice;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
|
@ -52,7 +52,17 @@
|
|||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="nb1-jq-97L"/>
|
||||
<menuItem title="About Background Music" tag="3" id="R45-Vo-Eto">
|
||||
<menuItem title="Status Bar Icon" enabled="NO" id="CmD-ot-1wE">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Background Music Logo" state="on" tag="2" id="9VF-qy-6fh">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Volume Icon" tag="3" toolTip="todo" id="B47-O2-wd0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="pYP-Fy-nKA"/>
|
||||
<menuItem title="About Background Music" tag="4" id="R45-Vo-Eto">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
</items>
|
||||
|
@ -205,7 +215,7 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="Cdb-RA-YK0">
|
||||
<rect key="frame" x="1" y="1" width="565" height="243"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="LSG-PF-cl8">
|
||||
<rect key="frame" x="-6" y="0.0" width="577" height="243"/>
|
||||
|
@ -316,7 +326,7 @@
|
|||
</objects>
|
||||
<resources>
|
||||
<image name="FermataIcon" width="284" height="284"/>
|
||||
<image name="NSComputer" width="128" height="128"/>
|
||||
<image name="NSComputer" width="32" height="32"/>
|
||||
<image name="buttonCell:IXo-C7-3uE:image" width="1" height="1">
|
||||
<mutableData key="keyedArchiveRepresentation">
|
||||
YnBsaXN0MDDUAQIDBAUGPT5YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK4HCBMU
|
||||
|
|
12
BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Contents.json
vendored
Normal file
12
BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"filename" : "Volume0.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Volume0.pdf
vendored
Normal file
BIN
BGMApp/BGMApp/Images.xcassets/Volume0.imageset/Volume0.pdf
vendored
Normal file
Binary file not shown.
12
BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Contents.json
vendored
Normal file
12
BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"filename" : "Volume1.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Volume1.pdf
vendored
Normal file
BIN
BGMApp/BGMApp/Images.xcassets/Volume1.imageset/Volume1.pdf
vendored
Normal file
Binary file not shown.
12
BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Contents.json
vendored
Normal file
12
BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"filename" : "Volume2.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Volume2.pdf
vendored
Normal file
BIN
BGMApp/BGMApp/Images.xcassets/Volume2.imageset/Volume2.pdf
vendored
Normal file
Binary file not shown.
12
BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Contents.json
vendored
Normal file
12
BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"filename" : "Volume3.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Volume3.pdf
vendored
Normal file
BIN
BGMApp/BGMApp/Images.xcassets/Volume3.imageset/Volume3.pdf
vendored
Normal file
Binary file not shown.
|
@ -17,7 +17,7 @@
|
|||
// BGMPreferencesMenu.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2018 Kyle Neideck
|
||||
// Copyright © 2016, 2018, 2019 Kyle Neideck
|
||||
//
|
||||
// Handles the preferences menu UI. The user's preference changes are often passed directly to the driver rather
|
||||
// than to other BGMApp classes.
|
||||
|
@ -26,6 +26,7 @@
|
|||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
#import "BGMMusicPlayers.h"
|
||||
#import "BGMStatusBarItem.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
@ -38,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
||||
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
||||
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
||||
statusBarItem:(BGMStatusBarItem*)inStatusBarItem
|
||||
aboutPanel:(NSPanel*)inAboutPanel
|
||||
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
// BGMPreferencesMenu.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2018 Kyle Neideck
|
||||
// Copyright © 2016, 2018, 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
|
@ -32,11 +32,18 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
// Interface Builder tags
|
||||
static NSInteger const kPreferencesMenuItemTag = 1;
|
||||
static NSInteger const kAboutPanelMenuItemTag = 3;
|
||||
static NSInteger const kBGMIconMenuItemTag = 2;
|
||||
static NSInteger const kVolumeIconMenuItemTag = 3;
|
||||
static NSInteger const kAboutPanelMenuItemTag = 4;
|
||||
|
||||
@implementation BGMPreferencesMenu {
|
||||
// Menu sections
|
||||
// Menu sections/items
|
||||
BGMAutoPauseMusicPrefs* autoPauseMusicPrefs;
|
||||
NSMenuItem* bgmIconMenuItem;
|
||||
NSMenuItem* volumeIconMenuItem;
|
||||
|
||||
// The menu item you press to open BGMApp's main menu.
|
||||
BGMStatusBarItem* statusBarItem;
|
||||
|
||||
// The About Background Music window
|
||||
BGMAboutPanel* aboutPanel;
|
||||
|
@ -45,6 +52,7 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
|||
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
||||
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
||||
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
||||
statusBarItem:(BGMStatusBarItem*)inStatusBarItem
|
||||
aboutPanel:(NSPanel*)inAboutPanel
|
||||
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView {
|
||||
if ((self = [super init])) {
|
||||
|
@ -56,6 +64,21 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
|||
|
||||
aboutPanel = [[BGMAboutPanel alloc] initWithPanel:inAboutPanel licenseView:inAboutPanelLicenseView];
|
||||
|
||||
statusBarItem = inStatusBarItem;
|
||||
|
||||
// Set up the menu items under the "Status Bar Icon" heading.
|
||||
bgmIconMenuItem = [prefsMenu itemWithTag:kBGMIconMenuItemTag];
|
||||
bgmIconMenuItem.state =
|
||||
(statusBarItem.icon == BGMFermataStatusBarIcon) ? NSOnState : NSOffState;
|
||||
[bgmIconMenuItem setTarget:self];
|
||||
[bgmIconMenuItem setAction:@selector(useBGMStatusBarIcon)];
|
||||
|
||||
volumeIconMenuItem = [prefsMenu itemWithTag:kVolumeIconMenuItemTag];
|
||||
volumeIconMenuItem.state =
|
||||
(statusBarItem.icon == BGMVolumeStatusBarIcon) ? NSOnState : NSOffState;
|
||||
[volumeIconMenuItem setTarget:self];
|
||||
[volumeIconMenuItem setAction:@selector(useVolumeStatusBarIcon)];
|
||||
|
||||
// Set up the "About Background Music" menu item
|
||||
NSMenuItem* aboutMenuItem = [prefsMenu itemWithTag:kAboutPanelMenuItemTag];
|
||||
[aboutMenuItem setTarget:aboutPanel];
|
||||
|
@ -65,6 +88,29 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void) useBGMStatusBarIcon {
|
||||
// Change the icon.
|
||||
statusBarItem.icon = BGMFermataStatusBarIcon;
|
||||
|
||||
// Select/deselect the menu items.
|
||||
bgmIconMenuItem.state = NSOnState;
|
||||
volumeIconMenuItem.state = NSOffState;
|
||||
}
|
||||
|
||||
- (void) useVolumeStatusBarIcon {
|
||||
// TODO: Maybe we should show a message that tells the user how to hide the built-in volume
|
||||
// icon. They probably won't want two status bar items that look the same. Or we might be
|
||||
// able to automatically hide the built-in icon while BGMApp is running and show it again
|
||||
// when BGMApp is closed.
|
||||
|
||||
// Change the icon.
|
||||
statusBarItem.icon = BGMVolumeStatusBarIcon;
|
||||
|
||||
// Select/deselect the menu items.
|
||||
bgmIconMenuItem.state = NSOffState;
|
||||
volumeIconMenuItem.state = NSOnState;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
69
Images/VolumeIcons.tex
Normal file
69
Images/VolumeIcons.tex
Normal file
|
@ -0,0 +1,69 @@
|
|||
% 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/>.
|
||||
|
||||
%
|
||||
% VolumeIcons.tex
|
||||
%
|
||||
% Build with XeTeX:
|
||||
% xelatex -jobname=Volume0 '\def\UseOption{}\input{VolumeIcons.tex}'
|
||||
% xelatex -jobname=Volume1 '\def\UseOption{w1}\input{VolumeIcons.tex}'
|
||||
% xelatex -jobname=Volume2 '\def\UseOption{w1,w2}\input{VolumeIcons.tex}'
|
||||
% xelatex -jobname=Volume3 '\def\UseOption{w1,w2,w3}\input{VolumeIcons.tex}'
|
||||
% for n in 0 1 2 3; do mv Volume$n.pdf ../BGMApp/BGMApp/Images.xcassets/Volume$n.imageset/; done
|
||||
%
|
||||
% Might build correctly with regular LaTeX. I haven't tried it.
|
||||
%
|
||||
|
||||
\documentclass[tikz]{standalone}
|
||||
\usepackage{tikz}
|
||||
% "dummyOption" prevents "Package optional Warning: No options were selected,
|
||||
% so all optional text will be printed" when building Volume0.pdf.
|
||||
\usepackage[dummyOption]{optional}
|
||||
|
||||
\begin{document}
|
||||
\begin{tikzpicture}
|
||||
|
||||
% Speaker (Rounded box and triangle)
|
||||
\fill[rounded corners=5mm]
|
||||
(0mm, 62.5mm) rectangle (25mm, 37.5mm) {};
|
||||
\draw[rounded corners=2.5mm,fill=black]
|
||||
(3mm, 50mm)--(34mm, 76.5mm)--(34mm, 23.5mm)--cycle;
|
||||
|
||||
% First sound wave (Curved line)
|
||||
\opt{w1}{
|
||||
\draw[line width=4.3mm,line cap=round]
|
||||
(44mm, 36.5mm) to[out=46,in=-46] (44mm, 63.5mm);
|
||||
}
|
||||
|
||||
% Second sound wave (Curved line)
|
||||
\opt{w2}{
|
||||
\draw[line width=4.3mm,line cap=round]
|
||||
(57.5mm, 27.5mm) to[out=46,in=-46] (57.5mm, 72.5mm);
|
||||
}
|
||||
|
||||
% Third sound wave (Curved line)
|
||||
\opt{w3}{
|
||||
\draw[line width=4.3mm,line cap=round]
|
||||
(72mm, 18.5mm) to[out=46,in=-46] (72mm, 81.5mm);
|
||||
}
|
||||
|
||||
% Always draw a transparent copy of the third wave so the images will all have
|
||||
% the same width.
|
||||
\draw[line width=4.3mm,line cap=round,opacity=0]
|
||||
(72mm, 18.5mm) to[out=46,in=-46] (72mm, 81.5mm);
|
||||
|
||||
\end{tikzpicture}
|
||||
\end{document}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
// BGM_Types.h
|
||||
// SharedSource
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016, 2017, 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef SharedSource__BGM_Types
|
||||
|
@ -77,7 +77,7 @@ enum
|
|||
|
||||
// AudioObjectPropertyElement docs: "Elements are numbered sequentially where 0 represents the
|
||||
// master element."
|
||||
static const AudioObjectPropertyElement kMasterChannel = 0;
|
||||
static const AudioObjectPropertyElement kMasterChannel = kAudioObjectPropertyElementMaster;
|
||||
|
||||
#pragma BGM Plug-in Custom Properties
|
||||
|
||||
|
|
Loading…
Reference in a new issue