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/
|
cmake-build-debug/
|
||||||
/Background-Music-*/
|
/Background-Music-*/
|
||||||
BGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
|
BGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
|
||||||
|
Images/*.aux
|
||||||
|
Images/*.log
|
||||||
|
|
||||||
# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore
|
# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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"; }; };
|
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"; }; };
|
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"; }; };
|
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 */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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>"; };
|
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>"; };
|
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>"; };
|
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 */,
|
1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */,
|
||||||
1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,
|
1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,
|
||||||
1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,
|
1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,
|
||||||
|
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */,
|
||||||
|
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */,
|
||||||
1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,
|
1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,
|
||||||
1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */,
|
1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */,
|
||||||
2743C9ED1D8538700089613B /* BGMUserDefaults.h */,
|
2743C9ED1D8538700089613B /* BGMUserDefaults.h */,
|
||||||
2743C9F01D853FBB0089613B /* BGMUserDefaults.m */,
|
2743C9F01D853FBB0089613B /* BGMUserDefaults.m */,
|
||||||
|
19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */,
|
||||||
|
19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */,
|
||||||
2795973C1C982E8C00A002FB /* BGMXPCListener.h */,
|
2795973C1C982E8C00A002FB /* BGMXPCListener.h */,
|
||||||
2795973A1C982E4E00A002FB /* BGMXPCListener.mm */,
|
2795973A1C982E4E00A002FB /* BGMXPCListener.mm */,
|
||||||
1C2FC3161EC7078F00A76592 /* Scripting */,
|
1C2FC3161EC7078F00A76592 /* Scripting */,
|
||||||
|
@ -1004,6 +1018,8 @@
|
||||||
2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */,
|
2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */,
|
||||||
27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */,
|
27C457E61CF2BC2600A6C9A6 /* BGMAutoPauseMenuItem.m in Sources */,
|
||||||
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */,
|
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */,
|
||||||
|
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */,
|
||||||
|
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1061,6 +1077,8 @@
|
||||||
1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */,
|
1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */,
|
||||||
1C2FC31C1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */,
|
1C2FC31C1EC7238A00A76592 /* BGMASOutputDevice.mm in Sources */,
|
||||||
1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,
|
1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,
|
||||||
|
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */,
|
||||||
|
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -1134,6 +1152,8 @@
|
||||||
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
|
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
|
||||||
2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,
|
2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,
|
||||||
2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,
|
2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,
|
||||||
|
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */,
|
||||||
|
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
// BGMAppDelegate.mm
|
// BGMAppDelegate.mm
|
||||||
// BGMApp
|
// BGMApp
|
||||||
//
|
//
|
||||||
// Copyright © 2016-2018 Kyle Neideck
|
// Copyright © 2016-2019 Kyle Neideck
|
||||||
//
|
//
|
||||||
|
|
||||||
// Self Includes
|
// Self Include
|
||||||
#import "BGMAppDelegate.h"
|
#import "BGMAppDelegate.h"
|
||||||
|
|
||||||
// Local Includes
|
// Local Includes
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
#import "BGMOutputVolumeMenuItem.h"
|
#import "BGMOutputVolumeMenuItem.h"
|
||||||
#import "BGMPreferencesMenu.h"
|
#import "BGMPreferencesMenu.h"
|
||||||
#import "BGMPreferredOutputDevices.h"
|
#import "BGMPreferredOutputDevices.h"
|
||||||
|
#import "BGMStatusBarItem.h"
|
||||||
#import "BGMSystemSoundsVolume.h"
|
#import "BGMSystemSoundsVolume.h"
|
||||||
#import "BGMTermination.h"
|
#import "BGMTermination.h"
|
||||||
#import "BGMUserDefaults.h"
|
#import "BGMUserDefaults.h"
|
||||||
|
@ -45,18 +46,19 @@
|
||||||
|
|
||||||
#pragma clang assume_nonnull begin
|
#pragma clang assume_nonnull begin
|
||||||
|
|
||||||
static float const kStatusBarIconPadding = 0.25;
|
|
||||||
static NSString* const kOptNoPersistentData = @"--no-persistent-data";
|
static NSString* const kOptNoPersistentData = @"--no-persistent-data";
|
||||||
static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
|
|
||||||
@implementation BGMAppDelegate {
|
@implementation BGMAppDelegate {
|
||||||
// The button in the system status bar (the bar with volume, battery, clock, etc.) to show the main menu
|
// The button in the system status bar that shows the main menu.
|
||||||
// for the app. These are called "menu bar extras" in the Human Interface Guidelines.
|
BGMStatusBarItem* statusBarItem;
|
||||||
NSStatusItem* statusBarItem;
|
|
||||||
|
|
||||||
// Only show the 'BGMXPCHelper is missing' error dialog once.
|
// Only show the 'BGMXPCHelper is missing' error dialog once.
|
||||||
BOOL haveShownXPCHelperErrorMessage;
|
BOOL haveShownXPCHelperErrorMessage;
|
||||||
|
|
||||||
|
// Persistently stores user settings and data.
|
||||||
|
BGMUserDefaults* userDefaults;
|
||||||
|
|
||||||
BGMAutoPauseMusic* autoPauseMusic;
|
BGMAutoPauseMusic* autoPauseMusic;
|
||||||
BGMAutoPauseMenuItem* autoPauseMenuItem;
|
BGMAutoPauseMenuItem* autoPauseMenuItem;
|
||||||
BGMMusicPlayers* musicPlayers;
|
BGMMusicPlayers* musicPlayers;
|
||||||
|
@ -71,6 +73,8 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
@synthesize audioDevices = audioDevices;
|
@synthesize audioDevices = audioDevices;
|
||||||
|
|
||||||
- (void) awakeFromNib {
|
- (void) awakeFromNib {
|
||||||
|
[super awakeFromNib];
|
||||||
|
|
||||||
// Show BGMApp in the dock, if the command-line option for that was passed. This is used by the
|
// Show BGMApp in the dock, if the command-line option for that was passed. This is used by the
|
||||||
// UI tests.
|
// UI tests.
|
||||||
if ([NSProcessInfo.processInfo.arguments indexOfObject:kOptShowDockIcon] != NSNotFound) {
|
if ([NSProcessInfo.processInfo.arguments indexOfObject:kOptShowDockIcon] != NSNotFound) {
|
||||||
|
@ -79,72 +83,19 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
|
|
||||||
haveShownXPCHelperErrorMessage = NO;
|
haveShownXPCHelperErrorMessage = NO;
|
||||||
|
|
||||||
[self initStatusBarItem];
|
// Set up audioDevices, which coordinates BGMDevice and the output device. It manages
|
||||||
}
|
// playthrough, volume/mute controls, etc.
|
||||||
|
if (![self initAudioDeviceManager]) {
|
||||||
// Set up the status bar item. (The thing you click to show BGMApp's UI.)
|
return;
|
||||||
- (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 the icon.
|
// Stored user settings
|
||||||
NSImage* icon = [NSImage imageNamed:@"FermataIcon"];
|
userDefaults = [self createUserDefaults];
|
||||||
|
|
||||||
if (icon != nil) {
|
// Add the status bar item. (The thing you click to show BGMApp's main menu.)
|
||||||
NSRect statusBarItemFrame;
|
statusBarItem = [[BGMStatusBarItem alloc] initWithMenu:self.bgmMenu
|
||||||
|
audioDevices:audioDevices
|
||||||
if (buttonAvailable) {
|
userDefaults:userDefaults];
|
||||||
#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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
|
- (void) applicationDidFinishLaunching:(NSNotification*)aNotification {
|
||||||
|
@ -159,15 +110,6 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"],
|
NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"],
|
||||||
NSBundle.mainBundle.infoDictionary[@"CFBundleVersion"]);
|
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
|
// Handles changing (or not changing) the output device when devices are added or removed. Must
|
||||||
// be initialised before calling setBGMDeviceAsDefault.
|
// be initialised before calling setBGMDeviceAsDefault.
|
||||||
preferredOutputDevices =
|
preferredOutputDevices =
|
||||||
|
@ -191,7 +133,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
|
autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
|
||||||
musicPlayers:musicPlayers];
|
musicPlayers:musicPlayers];
|
||||||
|
|
||||||
[self setUpMainMenu:userDefaults];
|
[self setUpMainMenu];
|
||||||
|
|
||||||
xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
|
xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
|
||||||
helperConnectionErrorHandler:^(NSError* error) {
|
helperConnectionErrorHandler:^(NSError* error) {
|
||||||
|
@ -285,7 +227,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) setUpMainMenu:(BGMUserDefaults*)userDefaults {
|
- (void) setUpMainMenu {
|
||||||
autoPauseMenuItem =
|
autoPauseMenuItem =
|
||||||
[[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped
|
[[BGMAutoPauseMenuItem alloc] initWithMenuItem:self.autoPauseMenuItemUnwrapped
|
||||||
autoPauseMusic:autoPauseMusic
|
autoPauseMusic:autoPauseMusic
|
||||||
|
@ -305,6 +247,7 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||||
prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu
|
prefsMenu = [[BGMPreferencesMenu alloc] initWithBGMMenu:self.bgmMenu
|
||||||
audioDevices:audioDevices
|
audioDevices:audioDevices
|
||||||
musicPlayers:musicPlayers
|
musicPlayers:musicPlayers
|
||||||
|
statusBarItem:statusBarItem
|
||||||
aboutPanel:self.aboutPanel
|
aboutPanel:self.aboutPanel
|
||||||
aboutPanelLicenseView:self.aboutPanelLicenseView];
|
aboutPanelLicenseView:self.aboutPanelLicenseView];
|
||||||
|
|
||||||
|
|
|
@ -202,7 +202,7 @@ static NSInteger const kOutputDeviceMenuItemTag = 5;
|
||||||
NSMutableArray<NSMenuItem*>* items = [NSMutableArray new];
|
NSMutableArray<NSMenuItem*>* items = [NSMutableArray new];
|
||||||
|
|
||||||
AudioObjectPropertyScope scope = kAudioObjectPropertyScopeOutput;
|
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
|
// 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
|
// for the device. This way the menu items' titles will be, for example, "Internal Speakers" rather
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
// BGMOutputVolumeMenuItem.mm
|
// BGMOutputVolumeMenuItem.mm
|
||||||
// BGMApp
|
// BGMApp
|
||||||
//
|
//
|
||||||
// Copyright © 2017, 2018 Kyle Neideck
|
// Copyright © 2017-2019 Kyle Neideck
|
||||||
//
|
//
|
||||||
|
|
||||||
// Self Include
|
// Self Include
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
// Local Includes
|
// Local Includes
|
||||||
#import "BGM_Utils.h"
|
#import "BGM_Utils.h"
|
||||||
#import "BGMAudioDevice.h"
|
#import "BGMAudioDevice.h"
|
||||||
|
#import "BGMVolumeChangeListener.h"
|
||||||
|
|
||||||
// PublicUtility Includes
|
// PublicUtility Includes
|
||||||
#import "CAException.h"
|
#import "CAException.h"
|
||||||
|
@ -46,7 +47,7 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
||||||
NSTextField* deviceLabel;
|
NSTextField* deviceLabel;
|
||||||
NSSlider* volumeSlider;
|
NSSlider* volumeSlider;
|
||||||
BGMAudioDevice outputDevice;
|
BGMAudioDevice outputDevice;
|
||||||
AudioObjectPropertyListenerBlock updateSliderListenerBlock;
|
BGMVolumeChangeListener* volumeChangeListener;
|
||||||
AudioObjectPropertyListenerBlock updateLabelListenerBlock;
|
AudioObjectPropertyListenerBlock updateLabelListenerBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,9 +67,8 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
||||||
volumeSlider = slider;
|
volumeSlider = slider;
|
||||||
outputDevice = audioDevices.outputDevice;
|
outputDevice = audioDevices.outputDevice;
|
||||||
|
|
||||||
// These are initialised in the methods called below.
|
// volumeChangeListener and updateLabelListenerBlock are initialised in the methods called
|
||||||
updateSliderListenerBlock = nil;
|
// below.
|
||||||
updateLabelListenerBlock = nil;
|
|
||||||
|
|
||||||
// Apply our custom view from MainMenu.xib.
|
// Apply our custom view from MainMenu.xib.
|
||||||
self.view = view;
|
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
|
// TODO: This call isn't thread safe. (But currently this dealloc method is only called if
|
||||||
// there's an error.)
|
// there's an error.)
|
||||||
[self removeOutputDeviceDataSourceListener];
|
[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 {
|
- (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
|
// Register a listener that will update the slider when the user changes the volume or
|
||||||
// mutes/unmutes their audio.
|
// mutes/unmutes their audio.
|
||||||
BGMOutputVolumeMenuItem* __weak weakSelf = self;
|
BGMOutputVolumeMenuItem* __weak weakSelf = self;
|
||||||
|
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
|
||||||
updateSliderListenerBlock =
|
[weakSelf updateVolumeSlider];
|
||||||
^(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);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the value of the output volume slider. Should only be called on the main thread because
|
// 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;
|
volumeSlider.doubleValue = 0.0;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
};
|
}
|
||||||
|
|
||||||
- (void) addOutputDeviceDataSourceListener {
|
- (void) addOutputDeviceDataSourceListener {
|
||||||
// Create the block that updates deviceLabel when the output device's data source changes, e.g.
|
// 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
|
// BGMUserDefaults.h
|
||||||
// BGMApp
|
// 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
|
// 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.
|
// apply to BGMApp. The others are stored by BGMDriver.
|
||||||
//
|
//
|
||||||
|
|
||||||
// System includes
|
// Local Includes
|
||||||
|
#import "BGMStatusBarItem.h"
|
||||||
|
|
||||||
|
// System Includes
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +47,10 @@
|
||||||
// device is at index 0. See BGMPreferredOutputDevices.
|
// device is at index 0. See BGMPreferredOutputDevices.
|
||||||
@property NSArray<NSString*>* preferredDeviceUIDs;
|
@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
|
@end
|
||||||
|
|
||||||
#pragma clang assume_nonnull end
|
#pragma clang assume_nonnull end
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
// BGMUserDefaults.m
|
// BGMUserDefaults.m
|
||||||
// BGMApp
|
// BGMApp
|
||||||
//
|
//
|
||||||
// Copyright © 2016-2018 Kyle Neideck
|
// Copyright © 2016-2019 Kyle Neideck
|
||||||
//
|
//
|
||||||
|
|
||||||
// Self Include
|
// Self Include
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
// Keys
|
// Keys
|
||||||
static NSString* const BGMDefaults_AutoPauseMusicEnabled = @"AutoPauseMusicEnabled";
|
static NSString* const BGMDefaults_AutoPauseMusicEnabled = @"AutoPauseMusicEnabled";
|
||||||
static NSString* const BGMDefaults_SelectedMusicPlayerID = @"SelectedMusicPlayerID";
|
static NSString* const BGMDefaults_SelectedMusicPlayerID = @"SelectedMusicPlayerID";
|
||||||
static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||||
|
static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||||
|
|
||||||
@implementation BGMUserDefaults {
|
@implementation BGMUserDefaults {
|
||||||
// The defaults object wrapped by this object.
|
// The defaults object wrapped by this object.
|
||||||
|
@ -89,6 +90,22 @@ static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||||
[self set:BGMDefaults_PreferredDeviceUIDs to:devices];
|
[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
|
#pragma mark Implementation
|
||||||
|
|
||||||
- (id __nullable) get:(NSString*)key {
|
- (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 {
|
- (BOOL) getBool:(NSString*)key {
|
||||||
return defaults ? [defaults boolForKey:key] : [transientDefaults[key] boolValue];
|
return defaults ? [defaults boolForKey:key] : [transientDefaults[key] boolValue];
|
||||||
}
|
}
|
||||||
|
@ -111,7 +129,32 @@ static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||||
if (defaults) {
|
if (defaults) {
|
||||||
[defaults setBool:value forKey:key];
|
[defaults setBool:value forKey:key];
|
||||||
} else {
|
} 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"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="nb1-jq-97L"/>
|
<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"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
</items>
|
</items>
|
||||||
|
@ -205,7 +215,7 @@
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="Cdb-RA-YK0">
|
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="Cdb-RA-YK0">
|
||||||
<rect key="frame" x="1" y="1" width="565" height="243"/>
|
<rect key="frame" x="1" y="1" width="565" height="243"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="LSG-PF-cl8">
|
<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"/>
|
<rect key="frame" x="-6" y="0.0" width="577" height="243"/>
|
||||||
|
@ -316,7 +326,7 @@
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="FermataIcon" width="284" height="284"/>
|
<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">
|
<image name="buttonCell:IXo-C7-3uE:image" width="1" height="1">
|
||||||
<mutableData key="keyedArchiveRepresentation">
|
<mutableData key="keyedArchiveRepresentation">
|
||||||
YnBsaXN0MDDUAQIDBAUGPT5YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK4HCBMU
|
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
|
// BGMPreferencesMenu.h
|
||||||
// BGMApp
|
// 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
|
// Handles the preferences menu UI. The user's preference changes are often passed directly to the driver rather
|
||||||
// than to other BGMApp classes.
|
// than to other BGMApp classes.
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
// Local Includes
|
// Local Includes
|
||||||
#import "BGMAudioDeviceManager.h"
|
#import "BGMAudioDeviceManager.h"
|
||||||
#import "BGMMusicPlayers.h"
|
#import "BGMMusicPlayers.h"
|
||||||
|
#import "BGMStatusBarItem.h"
|
||||||
|
|
||||||
// System Includes
|
// System Includes
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
@ -38,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
||||||
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
||||||
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
||||||
|
statusBarItem:(BGMStatusBarItem*)inStatusBarItem
|
||||||
aboutPanel:(NSPanel*)inAboutPanel
|
aboutPanel:(NSPanel*)inAboutPanel
|
||||||
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView;
|
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
// BGMPreferencesMenu.mm
|
// BGMPreferencesMenu.mm
|
||||||
// BGMApp
|
// BGMApp
|
||||||
//
|
//
|
||||||
// Copyright © 2016, 2018 Kyle Neideck
|
// Copyright © 2016, 2018, 2019 Kyle Neideck
|
||||||
//
|
//
|
||||||
|
|
||||||
// Self Include
|
// Self Include
|
||||||
|
@ -32,11 +32,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
// Interface Builder tags
|
// Interface Builder tags
|
||||||
static NSInteger const kPreferencesMenuItemTag = 1;
|
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 {
|
@implementation BGMPreferencesMenu {
|
||||||
// Menu sections
|
// Menu sections/items
|
||||||
BGMAutoPauseMusicPrefs* autoPauseMusicPrefs;
|
BGMAutoPauseMusicPrefs* autoPauseMusicPrefs;
|
||||||
|
NSMenuItem* bgmIconMenuItem;
|
||||||
|
NSMenuItem* volumeIconMenuItem;
|
||||||
|
|
||||||
|
// The menu item you press to open BGMApp's main menu.
|
||||||
|
BGMStatusBarItem* statusBarItem;
|
||||||
|
|
||||||
// The About Background Music window
|
// The About Background Music window
|
||||||
BGMAboutPanel* aboutPanel;
|
BGMAboutPanel* aboutPanel;
|
||||||
|
@ -45,6 +52,7 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
||||||
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
- (id) initWithBGMMenu:(NSMenu*)inBGMMenu
|
||||||
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
audioDevices:(BGMAudioDeviceManager*)inAudioDevices
|
||||||
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
musicPlayers:(BGMMusicPlayers*)inMusicPlayers
|
||||||
|
statusBarItem:(BGMStatusBarItem*)inStatusBarItem
|
||||||
aboutPanel:(NSPanel*)inAboutPanel
|
aboutPanel:(NSPanel*)inAboutPanel
|
||||||
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView {
|
aboutPanelLicenseView:(NSTextView*)inAboutPanelLicenseView {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
|
@ -56,6 +64,21 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
||||||
|
|
||||||
aboutPanel = [[BGMAboutPanel alloc] initWithPanel:inAboutPanel licenseView:inAboutPanelLicenseView];
|
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
|
// Set up the "About Background Music" menu item
|
||||||
NSMenuItem* aboutMenuItem = [prefsMenu itemWithTag:kAboutPanelMenuItemTag];
|
NSMenuItem* aboutMenuItem = [prefsMenu itemWithTag:kAboutPanelMenuItemTag];
|
||||||
[aboutMenuItem setTarget:aboutPanel];
|
[aboutMenuItem setTarget:aboutPanel];
|
||||||
|
@ -65,6 +88,29 @@ static NSInteger const kAboutPanelMenuItemTag = 3;
|
||||||
return self;
|
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
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_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
|
// BGM_Types.h
|
||||||
// SharedSource
|
// SharedSource
|
||||||
//
|
//
|
||||||
// Copyright © 2016, 2017 Kyle Neideck
|
// Copyright © 2016, 2017, 2019 Kyle Neideck
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef SharedSource__BGM_Types
|
#ifndef SharedSource__BGM_Types
|
||||||
|
@ -77,7 +77,7 @@ enum
|
||||||
|
|
||||||
// AudioObjectPropertyElement docs: "Elements are numbered sequentially where 0 represents the
|
// AudioObjectPropertyElement docs: "Elements are numbered sequentially where 0 represents the
|
||||||
// master element."
|
// master element."
|
||||||
static const AudioObjectPropertyElement kMasterChannel = 0;
|
static const AudioObjectPropertyElement kMasterChannel = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
#pragma BGM Plug-in Custom Properties
|
#pragma BGM Plug-in Custom Properties
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue