Compare commits

...

37 Commits

Author SHA1 Message Date
Kyle Neideck 3e528c7de0 Disable warnings in build_and_install.sh. 2017-06-21 08:31:56 +10:00
Kyle Neideck 03135dbc6e WIP: Add BGMDriver property for client PIDs and bundle IDs
The idea is to have BGMApp group all of an app's clients/subprocesses
together (the way Activity Monitor does if you set it to "app processes,
hierarchically") when setting app volumes.

For issue #42. (See that issue for much more detail.)
2016-08-02 01:04:12 +10:00
Kyle Neideck 679d624860 Add BGM_Driver tests: get/set the music player bundle ID property. 2016-07-04 16:32:59 +10:00
Kyle Neideck 55e9f60774 Merge branch 'hoke-t-Decibel'. Resolves #17. 2016-06-30 09:32:12 +10:00
Kyle Neideck e3fcbdb37e Add BGMDecibel files to the Xcode project. 2016-06-30 09:30:05 +10:00
Kyle Neideck 1ee9fa348e Fix build failure on case-sensitive file systems. Fixes #64.
It seems that BGMDriver was failing to compile on case-sensitive file
systems because BGM_Types.h included "AudioServerPlugin.h" instead of
"AudioServerPlugIn.h". (Lowercase "i".)

I tried building with the project and Xcode on a case-sensitive disk
image and it would fail without this patch. So I figure it should at
least build now. I haven't had time to test Background Music on a system
running on a case-insensitive file system yet, so I added a TODO about
it in TODO.md.

Also, some unrelated tidying up.
2016-06-16 18:38:29 +10:00
Kyle Neideck 3684483543 Merge branch 'Decibel' of https://github.com/hoke-t/BackgroundMusic into hoke-t-Decibel 2016-05-14 10:42:50 +10:00
Tanner Hoke 30a1735346 git rm extraneous Decibel files and use HTTPS links in README.md 2016-05-12 17:24:55 -05:00
Kyle Neideck 4dba9412fb Install NullAudio driver before tests run on Travis. 2016-05-11 10:02:31 +10:00
Kyle Neideck 90bceb9887 On Travis, quit BGMApp before running the tests.
Also, log the installed audio devices and their audio IDs.

I think the tests are probably failing because the Travis VMs don't have
any audio devices. If so, this won't actually fix the tests, but it
should help us narrow it down.
2016-05-11 02:34:54 +10:00
Kyle Neideck 6c9ca6e85c Merge pull request #53 from Piccirello/master
Add shortcut for changing output device from menu bar
2016-05-11 01:27:29 +10:00
Kyle Neideck 82ea2e4803 Merge pull request #27 from hoke-t/master
Disable the Auto-pause Music menu item if the selected music player isn't
running
2016-05-11 00:19:49 +10:00
Kyle Neideck bbe65a2431 Only pretend to disable the auto-pause menu item.
Instead of disabling the menu item when the music player isn't running,
just make it appear disabled. That way you can always disable auto-pause
without having to open your music player, but the UI still indicates
when it thinks the music player isn't running.
2016-05-11 00:08:46 +10:00
Kyle Neideck a229791ade Merge branch 'master' of https://github.com/hoke-t/BackgroundMusic into hoke-t-master 2016-05-10 07:06:57 +10:00
Piccirello be4135523e Add shortcut for changing output device from menu bar 2016-05-03 16:36:53 -04:00
Kyle Neideck b58ad2a1f8 Fix possible deadlock when starting IO.
BGM_Device::StartIO was holding the state mutex longer than it needed
to, which meant HasProperty, GetProperty, etc. couldn't return. If
BGMPlayThrough was notified about IO starting after StartIO locked the
mutex, BGMPlayThrough would get stuck trying to get one of BGMDevice's
properties.

Fixes #46.
2016-04-30 21:28:16 +10:00
Kyle Neideck 960fe0d28d Fix rare race condition in BGM_TaskQueue (hopefully).
Also enable a few more warnings in the BGMDriver project.
2016-04-30 20:50:29 +10:00
Kyle Neideck d827e7e0d8 Fix build script printing a git error when run outside of a git repo. 2016-04-29 13:29:19 +10:00
Kyle Neideck 34071e633f Add an install "one-liner" to the README.
Also fix a bug when running build_and_install.sh from a directory other
than the root of the project.
2016-04-29 13:18:52 +10:00
Kyle Neideck d021dff7a6 Merge pull request #50 from ZV95/patch-1
Addition of Wiki Install Instructions
2016-04-29 12:20:27 +10:00
Kyle Neideck e95f371305 Add debug build option to build script.
Also add info about logging to CONTRIBUTING.md.
2016-04-29 12:09:37 +10:00
Kyle Neideck 6fb281c3ce Merge pull request #30 from IgorMarques/improve/readme
Improve install instructions
2016-04-29 10:57:07 +10:00
Kyle Neideck 3f62b012c3 Fix build script failure if Xcode check finishes before install starts. 2016-04-29 07:41:06 +10:00
ZV95 c70a22dd24 Addition of Wiki Install Instructions
Tried to make a simple install instructional Wiki for those who are confused about installation. Simply adding a link to that wiki in this ReadMe.
2016-04-28 13:22:38 -04:00
Kyle Neideck ccb709fc02 Fix "sudo -v" in uninstall.sh causing Travis builds to fail. 2016-04-28 10:11:19 +10:00
Kyle Neideck 480d769c26 Fix race condition in build script. Also avoid 'sudo -v' on Travis CI. 2016-04-28 09:33:23 +10:00
Kyle Neideck 3eac1f5dab Allow sudo on Travis CI. 2016-04-28 08:49:35 +10:00
Kyle Neideck eda3505f1a Add initial .travis.yml to see if it works. 2016-04-27 02:49:35 +10:00
Kyle Neideck 44082ac920 Add manual installation instructions (mostly for troubleshooting) 2016-04-27 02:31:14 +10:00
Kyle Neideck f2a0898590 Build script: Check Xcode version in the background to launch quicker. 2016-04-27 02:28:47 +10:00
Kyle Neideck b707513e49 Lots of small improvements to the build script.
- Clean before installing. (Mostly to get full logs every time.)
- Clearer error messages.
- Better checking for Xcode/xcodebuild.
- Log extra system info.
- A number of minor bug fixes.
2016-04-26 21:04:13 +10:00
Kyle Neideck 8acc5d4c9e Change the min and max sample rates in BGM_Driver. 2016-04-25 08:38:45 +10:00
Tanner Hoke ad5fe4ecbb Update README.md 2016-04-19 10:53:10 -05:00
Tanner Hoke 7cc0d19182 Put music files in the correct group. 2016-04-19 10:43:24 -05:00
Igor Marques 9a5ed7c2b5 Improve install instructions 2016-04-19 10:06:27 -03:00
Tanner Hoke 5981e05bb1 Add support for Decibel 2016-04-18 21:37:29 -05:00
Tanner Hoke e87c43dc52 Disable the Auto-pause Music menu item if the selected music player isn't running 2016-04-18 20:15:28 -05:00
46 changed files with 6347 additions and 281 deletions
+23
View File
@@ -0,0 +1,23 @@
language: objective-c
os: osx
osx_image: xcode7.3
xcode_sdk: macosx10.11
sudo: required
branches:
only:
- master
install:
- sudo xcodebuild -project BGMApp/BGMAppTests/NullAudio/AudioDriverExamples.xcodeproj -target NullAudio DSTROOT="/" install
- sudo launchctl kickstart -kp system/com.apple.audio.coreaudiod || sudo killall coreaudiod
script:
- yes | ./build_and_install.sh
- cat build_and_install.log
- find */build/Release/*/ -type f -exec md5 {} \;
- system_profiler SPAudioDataType
- say -a '?'
- osascript -e 'tell application "Background Music" to quit'
- xcodebuild -workspace BGM.xcworkspace -scheme 'Background Music Device' test
- xcodebuild -workspace BGM.xcworkspace -scheme 'Background Music' test
- xcodebuild -workspace BGM.xcworkspace -scheme 'BGMXPCHelper' test
- yes | ./uninstall.sh
- ls /Applications /Library/Audio/Plug-Ins/HAL /usr/local/libexec /Library/Application\ Support/Background\ Music
+33 -6
View File
@@ -25,7 +25,6 @@
1C1963091BCAF677008A4DF7 /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963071BCAF677008A4DF7 /* CAHostTimeBase.cpp */; };
1C2336DA1BEAB6E7004C1C4E /* BGMMusicPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */; };
1C2336DF1BEAE10C004C1C4E /* BGMSpotify.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */; };
1C3DB4891BE0885A00EC8160 /* BGMAppVolumes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.mm */; };
1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C4699461BD5C0E400F78043 /* BGMiTunes.m */; };
1C46994E1BD7694C00F78043 /* BGMDeviceControlSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */; };
1CB8B33D1BBA75EF000E2DD1 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CB8B33C1BBA75EF000E2DD1 /* AppDelegate.mm */; };
@@ -48,12 +47,16 @@
276972911CB16008007A2F7C /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
276972921CB1603E007A2F7C /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
277170161CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 277170151CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m */; };
2783ABB71CE36CDB00F11FD4 /* BGMApps.m in Sources */ = {isa = PBXBuildFile; fileRef = 2783ABB61CE36CDB00F11FD4 /* BGMApps.m */; };
2783ABB81CE36CDB00F11FD4 /* BGMApps.m in Sources */ = {isa = PBXBuildFile; fileRef = 2783ABB61CE36CDB00F11FD4 /* BGMApps.m */; };
278D71E91CABB6FF00899CF9 /* BGMXPCHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 279597401C996E2000A002FB /* BGMXPCHelperTests.m */; };
278D71F61CABBC3B00899CF9 /* BGMXPCHelperTests-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 278D71F51CABBC3B00899CF9 /* BGMXPCHelperTests-Info.plist */; };
2795973B1C982E4E00A002FB /* BGMXPCListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2795973A1C982E4E00A002FB /* BGMXPCListener.mm */; };
27D643BE1C9FB84C00737F6E /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 27D643BB1C9FB84C00737F6E /* Info.plist */; };
27D643C01C9FB99200737F6E /* BGMXPCHelperService.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D643BA1C9FB84C00737F6E /* BGMXPCHelperService.m */; };
27D643C11C9FB99200737F6E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 27D643BC1C9FB84C00737F6E /* main.m */; };
27DEE37C1D4F8DA300DBFDF1 /* BGMAppVolumes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27DEE37A1D4F8DA300DBFDF1 /* BGMAppVolumes.mm */; };
27F7D4901D2483B100821C4B /* BGMDecibel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F7D48F1D2483B100821C4B /* BGMDecibel.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -113,8 +116,6 @@
1C2336DC1BEAB73F004C1C4E /* BGMMusicPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMMusicPlayer.h; path = "Music Players/BGMMusicPlayer.h"; sourceTree = "<group>"; };
1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMSpotify.h; path = "Music Players/BGMSpotify.h"; sourceTree = "<group>"; };
1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMSpotify.m; path = "Music Players/BGMSpotify.m"; sourceTree = "<group>"; };
1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAppVolumes.mm; sourceTree = "<group>"; };
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumes.h; sourceTree = "<group>"; };
1C4699461BD5C0E400F78043 /* BGMiTunes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMiTunes.m; path = "Music Players/BGMiTunes.m"; sourceTree = "<group>"; };
1C46994C1BD7694C00F78043 /* BGMDeviceControlSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMDeviceControlSync.cpp; sourceTree = "<group>"; };
1C46994D1BD7694C00F78043 /* BGMDeviceControlSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlSync.h; sourceTree = "<group>"; };
@@ -160,6 +161,8 @@
2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_Utils.h; path = ../SharedSource/BGM_Utils.h; sourceTree = "<group>"; };
277170141CA24D7C00AB34B4 /* BGMXPCListenerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMXPCListenerDelegate.h; path = BGMXPCHelper/BGMXPCListenerDelegate.h; sourceTree = SOURCE_ROOT; };
277170151CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMXPCListenerDelegate.m; path = BGMXPCHelper/BGMXPCListenerDelegate.m; sourceTree = SOURCE_ROOT; };
2783ABB51CE36CDB00F11FD4 /* BGMApps.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMApps.h; path = "App Volumes/BGMApps.h"; sourceTree = "<group>"; };
2783ABB61CE36CDB00F11FD4 /* BGMApps.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMApps.m; path = "App Volumes/BGMApps.m"; sourceTree = "<group>"; };
278D71F11CABB6FF00899CF9 /* BGMXPCHelperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMXPCHelperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
278D71F51CABBC3B00899CF9 /* BGMXPCHelperTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "BGMXPCHelperTests-Info.plist"; sourceTree = "<group>"; };
2795970D1C91589B00A002FB /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
@@ -173,6 +176,11 @@
27D643BB1C9FB84C00737F6E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = BGMXPCHelper/Info.plist; sourceTree = SOURCE_ROOT; };
27D643BC1C9FB84C00737F6E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = BGMXPCHelper/main.m; sourceTree = SOURCE_ROOT; };
27D643C41C9FBE5600737F6E /* BGM_TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGM_TestUtils.h; path = ../SharedSource/BGM_TestUtils.h; sourceTree = "<group>"; };
27DEE37A1D4F8DA300DBFDF1 /* BGMAppVolumes.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAppVolumes.mm; path = "App Volumes/BGMAppVolumes.mm"; sourceTree = "<group>"; };
27DEE37B1D4F8DA300DBFDF1 /* BGMAppVolumes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAppVolumes.h; path = "App Volumes/BGMAppVolumes.h"; sourceTree = "<group>"; };
27F7D48E1D2483B100821C4B /* BGMDecibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMDecibel.h; path = "Music Players/BGMDecibel.h"; sourceTree = "<group>"; };
27F7D48F1D2483B100821C4B /* BGMDecibel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMDecibel.m; path = "Music Players/BGMDecibel.m"; sourceTree = "<group>"; };
27F7D4911D2484A300821C4B /* Decibel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Decibel.h; path = "Music Players/Decibel.h"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -280,6 +288,8 @@
children = (
1C2336DC1BEAB73F004C1C4E /* BGMMusicPlayer.h */,
1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */,
27F7D48E1D2483B100821C4B /* BGMDecibel.h */,
27F7D48F1D2483B100821C4B /* BGMDecibel.m */,
1C2336DB1BEAB73F004C1C4E /* BGMiTunes.h */,
1C4699461BD5C0E400F78043 /* BGMiTunes.m */,
1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */,
@@ -325,8 +335,7 @@
children = (
1CB8B33B1BBA75EF000E2DD1 /* AppDelegate.h */,
1CB8B33C1BBA75EF000E2DD1 /* AppDelegate.mm */,
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */,
1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.mm */,
2783ABB01CE35C1D00F11FD4 /* App Volumes */,
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */,
1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */,
1C1465B91BCC49D1003AEFE6 /* BGMAutoPauseMusic.h */,
@@ -387,6 +396,7 @@
27379B841C7C53BE0084A24C /* Supporting Files */ = {
isa = PBXGroup;
children = (
27F7D4911D2484A300821C4B /* Decibel.h */,
27379B851C7C54870084A24C /* iTunes.h */,
27379B861C7C54870084A24C /* Spotify.h */,
27379B871C7C552A0084A24C /* VLC.h */,
@@ -420,6 +430,17 @@
name = "Supporting Files";
sourceTree = "<group>";
};
2783ABB01CE35C1D00F11FD4 /* App Volumes */ = {
isa = PBXGroup;
children = (
27DEE37B1D4F8DA300DBFDF1 /* BGMAppVolumes.h */,
27DEE37A1D4F8DA300DBFDF1 /* BGMAppVolumes.mm */,
2783ABB51CE36CDB00F11FD4 /* BGMApps.h */,
2783ABB61CE36CDB00F11FD4 /* BGMApps.m */,
);
name = "App Volumes";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -603,13 +624,15 @@
1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */,
1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */,
273F10DF1CC3D0B900C1C6DA /* BGMVox.m in Sources */,
27DEE37C1D4F8DA300DBFDF1 /* BGMAppVolumes.mm in Sources */,
1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */,
2783ABB71CE36CDB00F11FD4 /* BGMApps.m in Sources */,
1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */,
1C1962F41BCABFC5008A4DF7 /* CAHALAudioObject.cpp in Sources */,
27F7D4901D2483B100821C4B /* BGMDecibel.m in Sources */,
1C2336DA1BEAB6E7004C1C4E /* BGMMusicPlayer.m in Sources */,
1C1962F61BCABFC5008A4DF7 /* CAHALAudioSystemObject.cpp in Sources */,
1CC1DF821BE5068A00FB8FE4 /* CACFDictionary.cpp in Sources */,
1C3DB4891BE0885A00EC8160 /* BGMAppVolumes.mm in Sources */,
1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */,
1C1963061BCAF468008A4DF7 /* CAMutex.cpp in Sources */,
1CE7064C1BF1EC0600BFC06D /* BGMOutputDevicePrefs.mm in Sources */,
@@ -638,6 +661,7 @@
buildActionMask = 2147483647;
files = (
1CB8B3501BBA75F0000E2DD1 /* BGMAppTests.m in Sources */,
2783ABB81CE36CDB00F11FD4 /* BGMApps.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -722,6 +746,7 @@
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=0",
"CoreAudio_StopOnAssert=1",
"CoreAudio_ThreadStampMessages=0",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
@@ -817,6 +842,7 @@
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=0",
"CoreAudio_StopOnAssert=1",
"CoreAudio_ThreadStampMessages=0",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
@@ -876,6 +902,7 @@
"CoreAudio_Debug=0",
"CoreAudio_UseSysLog=0",
"CoreAudio_StopOnAssert=0",
"CoreAudio_ThreadStampMessages=0",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
@@ -27,7 +27,7 @@
#import <Cocoa/Cocoa.h>
@interface BGMAppVolumes : NSObject
@interface BGMAppVolumes : NSObject // todo: rename to BGMAppVolumesMenu
- (id) initWithMenu:(NSMenu*)menu appVolumeView:(NSView*)view audioDevices:(BGMAudioDeviceManager*)audioDevices;
@@ -14,7 +14,7 @@
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMAppVolumes.m
// BGMAppVolumes.mm
// BGMApp
//
// Copyright © 2016 Kyle Neideck
@@ -25,6 +25,7 @@
// BGM Includes
#include "BGM_Types.h"
#import "BGMApps.h"
// PublicUtility Includes
#include "CACFDictionary.h"
@@ -57,6 +58,10 @@ static float const kSlidersSnapWithin = 5;
forKeyPath:@"runningApplications"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
// todo: delete this
NSArray* clients = (__bridge NSArray*)[audioDevices bgmDevice].GetPropertyData_CFType(kBGMClientsAddress);
BGMApps* __unused apps = [[BGMApps alloc] initWithClients:clients];
}
return self;
@@ -90,9 +95,6 @@ static float const kSlidersSnapWithin = 5;
// TODO: Would it be better to only show apps that are registered as HAL clients?
if ([app activationPolicy] != NSApplicationActivationPolicyRegular) continue;
// Don't show Finder
if ([[app bundleIdentifier] isEqualTo:@"com.apple.finder"]) continue;
#ifndef NS_BLOCK_ASSERTIONS // If assertions are enabled
// Count how many apps we should add menu items for so we can check it at the end of the method
numApps++;
@@ -103,7 +105,7 @@ static float const kSlidersSnapWithin = 5;
// Look through the menu item's subviews for the ones we want to set up
for (NSView* subview in [[appVolItem view] subviews]) {
if ([subview conformsToProtocol:@protocol(BGMAppVolumeSubview)]) {
[subview performSelector:@selector(setUpWithApp:context:) withObject:app withObject:self];
[(NSView<BGMAppVolumeSubview>*)subview setUpWithApp:app context:self];
}
}
@@ -150,6 +152,7 @@ static float const kSlidersSnapWithin = 5;
}
}
// todo: split this method up so it can have a cleaner (less coupled) signature?
- (void) setVolumeOfMenuItem:(NSMenuItem*)menuItem fromAppVolumes:(CACFArray&)appVolumes {
// Set menuItem's volume slider to the volume of the app in appVolumes that menuItem represents
// Leaves menuItem unchanged if it doesn't match any of the apps in appVolumes
@@ -214,6 +217,7 @@ static float const kSlidersSnapWithin = 5;
}
}
// todo: move this into BGMApps. (it's not ui code.) should be able to remove the audioDevices instance var from this class then
- (void) sendVolumeChangeToBGMDevice:(SInt32)newVolume appProcessID:(pid_t)appProcessID appBundleID:(NSString*)appBundleID {
CACFDictionary appVolumeChange(true);
appVolumeChange.AddSInt32(CFSTR(kBGMAppVolumesKey_ProcessID), appProcessID);
+51
View File
@@ -0,0 +1,51 @@
// 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/>.
//
// BGMApps.h
// BGMApp
//
// Copyright © 2016 Kyle Neideck
//
// System Includes
#import <Foundation/Foundation.h>
@interface BGMApp : NSObject
- (id) initWithName:(NSString*)name;
@property NSString* name;
@property NSImage* icon;
@property Float32 volume;
//@property ? clients;
@property pid_t PID;
@property NSString* bundleID;
@property NSArray<BGMApp*>* children;
@end
@interface BGMApps : NSObject
// todo: comment (takes the data from kAudioDeviceCustomPropertyClients)
// todo: seems like this type could be stricter/better
- (id) initWithClients:(NSArray<NSDictionary*>*)clients;
@property (readonly) NSArray<BGMApp*>* apps;
@end
+40
View File
@@ -0,0 +1,40 @@
// 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/>.
//
// BGMApps.m
// BGMApp
//
// Copyright © 2016 Kyle Neideck
// todo: need to move any copyright notices around?
//
// Self Include
#import "BGMApps.h"
@implementation BGMApps
- (id) initWithClients:(NSArray<NSDictionary*>*)clients {
if ((self = [super init])) {
NSLog(@"%@", clients);
// todo
}
return self;
}
@end
+23 -32
View File
@@ -459,41 +459,32 @@ OSStatus BGMPlayThrough::BGMDeviceListenerProc(AudioObjectID inObjectID,
{
DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Got kAudioDevicePropertyDeviceIsRunning notification");
auto deviceIsRunningHandler = [refCon] {
// IsRunning doesn't always return true when IO is starting. Not sure why. But using
// RunningSomewhereOtherThanBGMApp instead seems to be working so far.
//
//if(refCon->mInputDevice->IsRunning())
if(RunningSomewhereOtherThanBGMApp(refCon->mInputDevice))
// This is dispatched because it can block and
// - we might be on a real-time thread, or
// - BGMXPCListener::waitForOutputDeviceToStartWithReply might get called on the same thread just
// before this and timeout waiting for this to run.
//
// TODO: We should find a way to do this without dispatching because dispatching isn't real-time safe.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
if(refCon->mActive)
{
#if DEBUG
refCon->mToldOutputDeviceToStartAt = mach_absolute_time();
#endif
refCon->Start();
}
};
CAMutex::Tryer stateTrier(refCon->mStateMutex);
if(stateTrier.HasLock())
{
// In the vast majority of cases (when we actually start playthrough here) we get the state lock
// and can invoke the handler directly
deviceIsRunningHandler();
}
else
{
// TODO: This should be rare, but we still shouldn't dispatch on the IO thread because it isn't
// real-time safe.
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
if(refCon->mActive)
DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Handling "
"kAudioDevicePropertyDeviceIsRunning notification in dispatched block");
CAMutex::Locker stateLocker(refCon->mStateMutex);
// IsRunning doesn't always return true when IO is starting. Not sure why. But using
// RunningSomewhereOtherThanBGMApp instead seems to be working so far.
//
//if(refCon->mInputDevice->IsRunning())
if(RunningSomewhereOtherThanBGMApp(refCon->mInputDevice))
{
DebugMsg("BGMPlayThrough::BGMDeviceListenerProc: Handling "
"kAudioDevicePropertyDeviceIsRunning notification in dispatched block");
CAMutex::Locker stateLocker(refCon->mStateMutex);
deviceIsRunningHandler();
#if DEBUG
refCon->mToldOutputDeviceToStartAt = mach_absolute_time();
#endif
refCon->Start();
}
});
}
}
});
}
break;
+15 -1
View File
@@ -175,8 +175,22 @@
}
- (void) waitForOutputDeviceToStartWithReply:(void (^)(NSError*))reply {
OSStatus err = [audioDevices waitForOutputDeviceToStart];
NSString* description;
OSStatus err;
try {
err = [audioDevices waitForOutputDeviceToStart];
} catch (CAException e) {
DebugMsg("BGMXPCListener::waitForOutputDeviceToStartWithReply: Caught CAException (%d). Replying kBGMXPC_HardwareError.",
e.GetError());
err = kBGMXPC_HardwareError;
} catch (...) {
DebugMsg("BGMXPCListener::waitForOutputDeviceToStartWithReply: Caught unknown exception. Replying kBGMXPC_InternalError.");
err = kBGMXPC_InternalError;
#if DEBUG
throw;
#endif
}
switch (err) {
case noErr:
+30
View File
@@ -0,0 +1,30 @@
// 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/>.
//
// BGMDecibel.h
// BGMApp
//
// Copyright © 2016 Kyle Neideck
//
// Superclass/Protocol Import
#import "BGMMusicPlayer.h"
@interface BGMDecibel : BGMMusicPlayer
@end
+93
View File
@@ -0,0 +1,93 @@
// 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/>.
//
// BGMDecibel.m
// BGMApp
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2016 Tanner Hoke
//
// Self Include
#import "BGMDecibel.h"
// Auto-generated Scripting Bridge header
#import "Decibel.h"
// PublicUtility Includes
#undef CoreAudio_ThreadStampMessages
#define CoreAudio_ThreadStampMessages 0 // Requires C++
#include "CADebugMacros.h"
@implementation BGMDecibel
BGM_MUSIC_PLAYER_DEFAULT_LOAD_METHOD
+ (NSString*) name {
return @"Decibel";
}
- (CFNumberRef) pid {
return NULL;
}
+ (CFStringRef) bundleID {
return CFSTR("org.sbooth.Decibel");
}
- (DecibelApplication* __nullable) decibel {
return (DecibelApplication*) self.sbApplication;
}
- (BOOL) isRunning {
return self.decibel && [self.decibel isRunning];
}
- (BOOL) pause {
// isPlaying checks isRunning, so we don't need to check it here and waste an Apple event
BOOL wasPlaying = [self isPlaying];
if (wasPlaying) {
DebugMsg("BGMDecibel::pause: Pausing Decibel");
[self.decibel pause];
}
return wasPlaying;
}
- (BOOL) unpause {
// isPaused checks isRunning, so we don't need to check it here and waste an Apple event
BOOL wasPaused = [self isPaused];
if (wasPaused) {
DebugMsg("BGMDecibel::unpause: Unpausing Decibel");
[self.decibel playPause];
}
return wasPaused;
}
- (BOOL) isPlaying {
return [self isRunning] && [self.decibel playing];
}
- (BOOL) isPaused {
return [self isRunning] && ![self.decibel playing];
}
@end
@@ -32,6 +32,11 @@
// The music player classes written so far use Scripting Bridge to communicate with the music
// player apps (see iTunes.h/Spotify.h) but any other way is fine too.
//
// If you're not sure what bundle ID the music player uses, install a debug build of BGMDriver
// and play something in the music player. The easiest way is to do
// build_and_install.sh -d
// BGMDriver will log the bundle ID to system.log when it becomes aware of the music player.
//
// System Includes
#import <Foundation/Foundation.h>
+1 -1
View File
@@ -107,7 +107,7 @@ static BGMMusicPlayer* sSelectedMusicPlayer;
return self;
}
- (id) eventDidFail:(const AppleEvent*)event withError:(NSError*)error {
- (id __nullable) eventDidFail:(const AppleEvent*)event withError:(NSError*)error {
// SBApplicationDelegate method. So far, this just logs the error.
#if DEBUG
+181
View File
@@ -0,0 +1,181 @@
/*
* Decibel.h
*
* Generated with
* sdef /Applications/Decibel.app | sdp -fh --basename Decibel
*/
#import <AppKit/AppKit.h>
#import <ScriptingBridge/ScriptingBridge.h>
@class DecibelApplication, DecibelDocument, DecibelWindow, DecibelApplication, DecibelTrack;
enum DecibelSaveOptions {
DecibelSaveOptionsYes = 'yes ' /* Save the file. */,
DecibelSaveOptionsNo = 'no ' /* Do not save the file. */,
DecibelSaveOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */
};
typedef enum DecibelSaveOptions DecibelSaveOptions;
enum DecibelPrintingErrorHandling {
DecibelPrintingErrorHandlingStandard = 'lwst' /* Standard PostScript error handling */,
DecibelPrintingErrorHandlingDetailed = 'lwdt' /* print a detailed report of PostScript errors */
};
typedef enum DecibelPrintingErrorHandling DecibelPrintingErrorHandling;
enum DecibelShuffleMode {
DecibelShuffleModeOff = 'off ' /* Off */,
DecibelShuffleModeTrack = 'trck' /* Track */,
DecibelShuffleModeAlbum = 'albm' /* Album */,
DecibelShuffleModeArtist = 'arts' /* Artist */
};
typedef enum DecibelShuffleMode DecibelShuffleMode;
enum DecibelRepeatMode {
DecibelRepeatModeOff = 'off ' /* Off */,
DecibelRepeatModeTrack = 'trck' /* Track */,
DecibelRepeatModeAlbum = 'albm' /* Album */,
DecibelRepeatModeArtist = 'arts' /* Artist */,
DecibelRepeatModeAll = 'all ' /* All */
};
typedef enum DecibelRepeatMode DecibelRepeatMode;
@protocol DecibelGenericMethods
- (void) closeSaving:(DecibelSaveOptions)saving savingIn:(NSURL *)savingIn; // Close a document.
- (void) saveIn:(NSURL *)in_ as:(id)as; // Save a document.
- (void) printWithProperties:(NSDictionary *)withProperties printDialog:(BOOL)printDialog; // Print a document.
- (void) delete; // Delete an object.
- (void) duplicateTo:(SBObject *)to withProperties:(NSDictionary *)withProperties; // Copy an object.
- (void) moveTo:(SBObject *)to; // Move an object to a new location.
@end
/*
* Standard Suite
*/
// The application's top-level scripting object.
@interface DecibelApplication : SBApplication
- (SBElementArray<DecibelDocument *> *) documents;
- (SBElementArray<DecibelWindow *> *) windows;
@property (copy, readonly) NSString *name; // The name of the application.
@property (readonly) BOOL frontmost; // Is this the active application?
@property (copy, readonly) NSString *version; // The version number of the application.
- (id) open:(id)x; // Open a document.
- (void) print:(id)x withProperties:(NSDictionary *)withProperties printDialog:(BOOL)printDialog; // Print a document.
- (void) quitSaving:(DecibelSaveOptions)saving; // Quit the application.
- (BOOL) exists:(id)x; // Verify that an object exists.
- (void) play; // Begin audio playback
- (void) pause; // Suspend audio playback
- (void) stop; // Stop audio playback
- (void) playPause; // Begin or suspend audio playback
- (void) seekForward; // Seek forward three seconds
- (void) seekBackward; // Seek backward three seconds
- (void) playSelection; // Play the selected track, or the first track if more than one are selected
- (void) playPreviousTrack; // Play the previous logical track in the playlist
- (void) playNextTrack; // Play the next logical track in the playlist
- (void) addFile:(NSURL *)x; // Add a file to the playlist
- (void) playFile:(NSURL *)x; // Add a file to the playlist and play it
- (void) playTrackAtIndex:(NSInteger)x; // Play a track in the playlist
- (void) increaseDeviceVolume; // Increase the device volume
- (void) decreaseDeviceVolume; // Decrease the device volume
- (void) increaseDigitalVolume; // Increase the digital volume
- (void) decreaseDigitalVolume; // Decrease the digital volume
- (void) clearPlaylist; // Clear the playlist
- (void) scramblePlaylist; // Scramble the playlist
@end
// A document.
@interface DecibelDocument : SBObject <DecibelGenericMethods>
@property (copy, readonly) NSString *name; // Its name.
@property (readonly) BOOL modified; // Has it been modified since the last save?
@property (copy, readonly) NSURL *file; // Its location on disk, if it has one.
@end
// A window.
@interface DecibelWindow : SBObject <DecibelGenericMethods>
@property (copy, readonly) NSString *name; // The title of the window.
- (NSInteger) id; // The unique identifier of the window.
@property NSInteger index; // The index of the window, ordered front to back.
@property NSRect bounds; // The bounding rectangle of the window.
@property (readonly) BOOL closeable; // Does the window have a close button?
@property (readonly) BOOL miniaturizable; // Does the window have a minimize button?
@property BOOL miniaturized; // Is the window minimized right now?
@property (readonly) BOOL resizable; // Can the window be resized?
@property BOOL visible; // Is the window visible right now?
@property (readonly) BOOL zoomable; // Does the window have a zoom button?
@property BOOL zoomed; // Is the window zoomed right now?
@property (copy, readonly) DecibelDocument *document; // The document whose contents are displayed in the window.
@end
/*
* Decibel Scripting Suite
*/
// The Decibel application class.
@interface DecibelApplication (DecibelScriptingSuite)
- (SBElementArray<DecibelTrack *> *) tracks;
@property (readonly) BOOL playing; // Is the player currently playing?
@property (readonly) BOOL shuffling; // Is the player currently shuffling?
@property (readonly) BOOL repeating; // Is the player currently repeating?
@property (copy, readonly) DecibelTrack *nowPlaying; // The track that is currently playing?
@property double deviceVolume; // The current device volume
@property double digitalVolume; // The current digital volume
@property double playbackPosition; // The current playback position [0, 1]
@property double playbackTime; // The current playback time in seconds
@property (readonly) BOOL canPlay; // Is the player currently playing?
@property (readonly) BOOL canPlayPreviousTrack; // Is the player currently playing?
@property (readonly) BOOL canPlayNextTrack; // Is the player currently playing?
@property (readonly) BOOL canAdjustDeviceVolume; // Can the device volume be adjusted?
@property DecibelShuffleMode shuffleMode; // Player shuffle mode
@property DecibelRepeatMode repeatMode; // Player repeat mode
@property (copy, readonly) SBObject *currentPlaylist; // The current playlist
@end
// A track in the playlist
@interface DecibelTrack : SBObject <DecibelGenericMethods>
- (NSString *) id; // The track's ID
@property (copy, readonly) NSURL *file; // The track's location
@property (readonly) double duration; // The track's duration in seconds
@property (readonly) double sampleRate; // The track's sample rate in Hz
@property (readonly) NSInteger bitDepth; // The bit depth
@property (readonly) NSInteger channels; // The track's channels
@property (copy) NSString *title; // The track's title
@property (copy) NSString *artist; // The track's artist
@property (copy) NSString *albumTitle; // The track's album title
@property (copy) NSString *albumArtist; // The track's album artist
@property NSInteger trackNumber; // The track's track number
@property NSInteger trackTotal; // The total number of tracks on the album
@property NSInteger discNumber; // The disc number containing the track
@property NSInteger discTotal; // The total number of discs (for multidisc albums)
@property BOOL partOfACompilation; // Is the track part of a compilation?
@property (copy) NSString *genre; // The track's genre
@property (copy) NSString *composer; // The track's composer
@property (copy) NSString *releaseDate; // The track's release date
@property (copy) NSString *ISRC; // The track's ISRC
@property (copy) id MCN; // The track's MCN
- (void) playTrack; // Play a track in the playlist
@end
@@ -18,6 +18,7 @@
// BGMApp
//
// Copyright © 2016 Kyle Neideck
// Copyright © 2016 Tanner Hoke
//
// Self Includes
@@ -29,6 +30,9 @@
static NSString* const kToggleAutoPauseMusicMenuItemTitleFormat = @"Auto-pause %@";
static NSString* const kToggleAutoPauseMusicMenuItemDisabledTooltipFormat = @"%@ doesn't appear to be running.";
// Wait time to disable/enable the auto-pause menu item, in seconds
static SInt64 const kToggleAutoPauseMusicMenuItemUpdateWaitTime = 1;
static float const kMenuItemIconScalingFactor = 1.15f;
static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
@@ -39,6 +43,8 @@ static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
NSArray<NSMenuItem*>* musicPlayerMenuItems;
}
id<NSObject> didLaunchToken, didTerminateToken;
- (id) initWithPreferencesMenu:(NSMenu*)inPrefsMenu
toggleAutoPauseMusicMenuItem:(NSMenuItem*)inToggleAutoPauseMusicMenuItem
audioDevices:(BGMAudioDeviceManager*)inAudioDevices {
@@ -48,6 +54,7 @@ static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
audioDevices = inAudioDevices;
musicPlayerMenuItems = @[];
[self initMusicPlayerObservers];
[self initSelectedMusicPlayer];
[self initMenuSection];
[self updateMenuItemTitle];
@@ -56,6 +63,35 @@ static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
return self;
}
- (void) initMusicPlayerObservers {
// Add observers that enable/disable the Auto-pause Music menu item when the music player is launched/terminated.
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
id<NSObject> (^addObserver)(NSString*) = ^(NSString* name) {
return [center addObserverForName:name
object:nil
queue:nil
usingBlock:^(NSNotification* note) {
NSString* appBundleID = [note.userInfo[NSWorkspaceApplicationKey] bundleIdentifier];
NSString* musicPlayerBundleID =
(__bridge NSString*)[[[BGMMusicPlayer selectedMusicPlayer] class] bundleID];
BOOL isAboutThisMusicPlayer = [appBundleID isEqualToString:musicPlayerBundleID];
if (isAboutThisMusicPlayer) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
kToggleAutoPauseMusicMenuItemUpdateWaitTime * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{
[self updateMenuItemTitle];
});
}
}];
};
didLaunchToken = addObserver(NSWorkspaceDidLaunchApplicationNotification);
didTerminateToken = addObserver(NSWorkspaceDidTerminateApplicationNotification);
}
- (void) initSelectedMusicPlayer {
// TODO: It would make more sense to either just save the music player setting in the User Defaults (the same way AppDelegate saves
// whether auto-pause is enabled) or to send a "musicPlayerID" to the driver, which would only be used by BGMApp. If the latter,
@@ -195,10 +231,50 @@ static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
}
- (void) updateMenuItemTitle {
// Set the title of the Auto-pause Music menu item, including the name of the selected music player
// Set the title of the Auto-pause Music menu item, including the name of the selected music player.
NSString* musicPlayerName = [[[BGMMusicPlayer selectedMusicPlayer] class] name];
NSString* title = [NSString stringWithFormat:kToggleAutoPauseMusicMenuItemTitleFormat, musicPlayerName];
[toggleAutoPauseMusicMenuItem setTitle:title];
// Make the Auto-pause Music menu item appear disabled if the application is not running.
//
// We don't actually disable it just in case the user decides to disable auto-pause and their music player isn't running. E.g.
// someone who only recently installed Background Music and doesn't want to use auto-pause at all.
if ([[BGMMusicPlayer selectedMusicPlayer] isRunning]) {
toggleAutoPauseMusicMenuItem.attributedTitle = nil;
toggleAutoPauseMusicMenuItem.toolTip = nil;
} else {
// Hardcode the text colour to match disabled menu items. I couldn't figure out a way to do this properly. There's no colour
// constant for this, except possibly disabledControlTextColor, which just leaves the text black for me. I also couldn't get
// the colours from the built-in NSColorLists.
//
// TODO: Is it possible to make the tick mark grey as well?
BOOL darkMode = [@"Dark" isEqualToString:[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]];
NSDictionary* attributes = @{ NSFontAttributeName: [NSFont menuBarFontOfSize:0], // Default font size
NSForegroundColorAttributeName: [NSColor colorWithHue:0
saturation:0
brightness:(darkMode ? 0.25 : 0.75)
alpha:1] };
NSAttributedString* pseudoDisabledTitle = [[NSAttributedString alloc] initWithString:[toggleAutoPauseMusicMenuItem title]
attributes:attributes];
[toggleAutoPauseMusicMenuItem setAttributedTitle:pseudoDisabledTitle];
toggleAutoPauseMusicMenuItem.toolTip =
[NSString stringWithFormat:kToggleAutoPauseMusicMenuItemDisabledTooltipFormat, musicPlayerName];
}
}
- (void) dealloc {
// Remove the application launch/termination observers.
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
if (didLaunchToken) {
[center removeObserver:didLaunchToken];
}
if (didTerminateToken) {
[center removeObserver:didTerminateToken];
}
}
@end
@@ -0,0 +1,754 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
2D46CA7B17D6ADCA00049D4A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2D46CA7817D6AD8A00049D4A /* Localizable.strings */; };
2D46CA7D17D6AF4200049D4A /* DeviceIcon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 2D46CA7C17D6AF4200049D4A /* DeviceIcon.icns */; };
2D47CAA415FEC82B002AAFB5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2D47CAA215FEC82B002AAFB5 /* Localizable.strings */; };
2D4DE41415EDF8D500E96F0D /* CAVolumeCurve.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2D4DE41215EDF8D500E96F0D /* CAVolumeCurve.cpp */; };
2D4DE41515EDF8D500E96F0D /* CAVolumeCurve.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D4DE41315EDF8D500E96F0D /* CAVolumeCurve.h */; };
2D616EF415B8C82500D598BD /* NullAudio.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D616EF215B8C82500D598BD /* NullAudio.c */; };
2D7477AD1578168D00412279 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7477AC1578168D00412279 /* CoreFoundation.framework */; };
2D76D96115E48B2000FF0F33 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7477AC1578168D00412279 /* CoreFoundation.framework */; };
2D76D97615E48B6400FF0F33 /* SA_PlugIn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2D76D97415E48B6400FF0F33 /* SA_PlugIn.cpp */; };
2D76D97715E48B6400FF0F33 /* SA_PlugIn.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D76D97515E48B6400FF0F33 /* SA_PlugIn.h */; };
2D76D97A15E498EB00FF0F33 /* SA_Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2D76D97815E498EB00FF0F33 /* SA_Object.cpp */; };
2D76D97B15E498EB00FF0F33 /* SA_Object.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D76D97915E498EB00FF0F33 /* SA_Object.h */; };
2DD7AA0A15EACDE000C67AE1 /* SA_IOKit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA0815EACDDF00C67AE1 /* SA_IOKit.cpp */; };
2DD7AA0B15EACDE000C67AE1 /* SA_IOKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA0915EACDDF00C67AE1 /* SA_IOKit.h */; };
2DD7AA2315EAFD5100C67AE1 /* CACFArray.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA0E15EAFD3300C67AE1 /* CACFArray.cpp */; };
2DD7AA2415EAFD5100C67AE1 /* CACFArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA0F15EAFD3300C67AE1 /* CACFArray.h */; };
2DD7AA2515EAFD5100C67AE1 /* CACFDictionary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1015EAFD3300C67AE1 /* CACFDictionary.cpp */; };
2DD7AA2615EAFD5100C67AE1 /* CACFDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1115EAFD3300C67AE1 /* CACFDictionary.h */; };
2DD7AA2715EAFD5100C67AE1 /* CACFNumber.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1215EAFD3300C67AE1 /* CACFNumber.cpp */; };
2DD7AA2815EAFD5100C67AE1 /* CACFNumber.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1315EAFD3300C67AE1 /* CACFNumber.h */; };
2DD7AA2915EAFD5100C67AE1 /* CACFString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1415EAFD3300C67AE1 /* CACFString.cpp */; };
2DD7AA2A15EAFD5100C67AE1 /* CACFString.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1515EAFD3300C67AE1 /* CACFString.h */; };
2DD7AA2B15EAFD5100C67AE1 /* CADebugger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1615EAFD3300C67AE1 /* CADebugger.cpp */; };
2DD7AA2C15EAFD5100C67AE1 /* CADebugger.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1715EAFD3300C67AE1 /* CADebugger.h */; };
2DD7AA2D15EAFD5100C67AE1 /* CADebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1815EAFD3300C67AE1 /* CADebugMacros.cpp */; };
2DD7AA2E15EAFD5100C67AE1 /* CADebugMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1915EAFD3300C67AE1 /* CADebugMacros.h */; };
2DD7AA2F15EAFD5100C67AE1 /* CADebugPrintf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1A15EAFD3300C67AE1 /* CADebugPrintf.cpp */; };
2DD7AA3015EAFD5100C67AE1 /* CADebugPrintf.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1B15EAFD3300C67AE1 /* CADebugPrintf.h */; };
2DD7AA3115EAFD5100C67AE1 /* CAException.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1C15EAFD3300C67AE1 /* CAException.h */; };
2DD7AA3215EAFD5100C67AE1 /* CAGuard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1D15EAFD3300C67AE1 /* CAGuard.cpp */; };
2DD7AA3315EAFD5100C67AE1 /* CAGuard.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA1E15EAFD3300C67AE1 /* CAGuard.h */; };
2DD7AA3415EAFD5100C67AE1 /* CAHostTimeBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA1F15EAFD3300C67AE1 /* CAHostTimeBase.cpp */; };
2DD7AA3515EAFD5100C67AE1 /* CAHostTimeBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA2015EAFD3300C67AE1 /* CAHostTimeBase.h */; };
2DD7AA3615EAFD5100C67AE1 /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA2115EAFD3300C67AE1 /* CAMutex.cpp */; };
2DD7AA3715EAFD5100C67AE1 /* CAMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA2215EAFD3300C67AE1 /* CAMutex.h */; };
2DD7AA7D15EC20FD00C67AE1 /* CADispatchQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA7B15EC20FD00C67AE1 /* CADispatchQueue.cpp */; };
2DD7AA7E15EC20FD00C67AE1 /* CADispatchQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA7C15EC20FD00C67AE1 /* CADispatchQueue.h */; };
2DD7AA8015EC3DB800C67AE1 /* CACFObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA7F15EC3DB800C67AE1 /* CACFObject.h */; };
2DD7AA9715EC551600C67AE1 /* SA_Device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DD7AA9515EC551500C67AE1 /* SA_Device.cpp */; };
2DD7AA9815EC551600C67AE1 /* SA_Device.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DD7AA9615EC551600C67AE1 /* SA_Device.h */; };
2DD7AA9A15EC572000C67AE1 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DD7AA9915EC572000C67AE1 /* IOKit.framework */; };
2DED184915C359AC0091BE97 /* SimpleAudioDriver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DED182515C356BA0091BE97 /* SimpleAudioDriver.cpp */; };
2DED184A15C359AC0091BE97 /* SimpleAudioDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DED182615C356BA0091BE97 /* SimpleAudioDriver.h */; };
2DED184B15C359AC0091BE97 /* SimpleAudioDriverUserClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DED182915C356BA0091BE97 /* SimpleAudioDriverUserClient.cpp */; };
2DED184C15C359AC0091BE97 /* SimpleAudioDriverUserClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DED182A15C356BA0091BE97 /* SimpleAudioDriverUserClient.h */; };
2DED184D15C359AC0091BE97 /* SimpleAudioDriverTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DED182B15C356BA0091BE97 /* SimpleAudioDriverTypes.h */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2D46CA7917D6AD8A00049D4A /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = "<group>"; };
2D46CA7C17D6AF4200049D4A /* DeviceIcon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = DeviceIcon.icns; sourceTree = "<group>"; };
2D47CAA315FEC82B002AAFB5 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = "<group>"; };
2D4DE41215EDF8D500E96F0D /* CAVolumeCurve.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CAVolumeCurve.cpp; sourceTree = "<group>"; };
2D4DE41315EDF8D500E96F0D /* CAVolumeCurve.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAVolumeCurve.h; sourceTree = "<group>"; };
2D616EF115B8C82500D598BD /* NullAudio-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "NullAudio-Info.plist"; sourceTree = "<group>"; };
2D616EF215B8C82500D598BD /* NullAudio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = NullAudio.c; sourceTree = "<group>"; };
2D7477A91578168D00412279 /* NullAudio.driver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NullAudio.driver; sourceTree = BUILT_PRODUCTS_DIR; };
2D7477AC1578168D00412279 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
2D7477EC157823CF00412279 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
2D76D96015E48B2000FF0F33 /* SimpleAudioPlugIn.driver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleAudioPlugIn.driver; sourceTree = BUILT_PRODUCTS_DIR; };
2D76D97415E48B6400FF0F33 /* SA_PlugIn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SA_PlugIn.cpp; sourceTree = "<group>"; };
2D76D97515E48B6400FF0F33 /* SA_PlugIn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SA_PlugIn.h; sourceTree = "<group>"; };
2D76D97815E498EB00FF0F33 /* SA_Object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SA_Object.cpp; sourceTree = "<group>"; };
2D76D97915E498EB00FF0F33 /* SA_Object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SA_Object.h; sourceTree = "<group>"; };
2D76D98B15E56E4E00FF0F33 /* SA_PlugIn-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SA_PlugIn-Info.plist"; sourceTree = "<group>"; };
2DA8FA1515FEAAB000F04B50 /* ReadMe.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ReadMe.txt; sourceTree = "<group>"; };
2DD7AA0815EACDDF00C67AE1 /* SA_IOKit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SA_IOKit.cpp; sourceTree = "<group>"; };
2DD7AA0915EACDDF00C67AE1 /* SA_IOKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SA_IOKit.h; sourceTree = "<group>"; };
2DD7AA0E15EAFD3300C67AE1 /* CACFArray.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CACFArray.cpp; sourceTree = "<group>"; };
2DD7AA0F15EAFD3300C67AE1 /* CACFArray.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CACFArray.h; sourceTree = "<group>"; };
2DD7AA1015EAFD3300C67AE1 /* CACFDictionary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CACFDictionary.cpp; sourceTree = "<group>"; };
2DD7AA1115EAFD3300C67AE1 /* CACFDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CACFDictionary.h; sourceTree = "<group>"; };
2DD7AA1215EAFD3300C67AE1 /* CACFNumber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CACFNumber.cpp; sourceTree = "<group>"; };
2DD7AA1315EAFD3300C67AE1 /* CACFNumber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CACFNumber.h; sourceTree = "<group>"; };
2DD7AA1415EAFD3300C67AE1 /* CACFString.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CACFString.cpp; sourceTree = "<group>"; };
2DD7AA1515EAFD3300C67AE1 /* CACFString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CACFString.h; sourceTree = "<group>"; };
2DD7AA1615EAFD3300C67AE1 /* CADebugger.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CADebugger.cpp; sourceTree = "<group>"; };
2DD7AA1715EAFD3300C67AE1 /* CADebugger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CADebugger.h; sourceTree = "<group>"; };
2DD7AA1815EAFD3300C67AE1 /* CADebugMacros.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CADebugMacros.cpp; sourceTree = "<group>"; };
2DD7AA1915EAFD3300C67AE1 /* CADebugMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CADebugMacros.h; sourceTree = "<group>"; };
2DD7AA1A15EAFD3300C67AE1 /* CADebugPrintf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CADebugPrintf.cpp; sourceTree = "<group>"; };
2DD7AA1B15EAFD3300C67AE1 /* CADebugPrintf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CADebugPrintf.h; sourceTree = "<group>"; };
2DD7AA1C15EAFD3300C67AE1 /* CAException.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAException.h; sourceTree = "<group>"; };
2DD7AA1D15EAFD3300C67AE1 /* CAGuard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAGuard.cpp; sourceTree = "<group>"; };
2DD7AA1E15EAFD3300C67AE1 /* CAGuard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAGuard.h; sourceTree = "<group>"; };
2DD7AA1F15EAFD3300C67AE1 /* CAHostTimeBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAHostTimeBase.cpp; sourceTree = "<group>"; };
2DD7AA2015EAFD3300C67AE1 /* CAHostTimeBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAHostTimeBase.h; sourceTree = "<group>"; };
2DD7AA2115EAFD3300C67AE1 /* CAMutex.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAMutex.cpp; sourceTree = "<group>"; };
2DD7AA2215EAFD3300C67AE1 /* CAMutex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CAMutex.h; sourceTree = "<group>"; };
2DD7AA7B15EC20FD00C67AE1 /* CADispatchQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CADispatchQueue.cpp; sourceTree = "<group>"; };
2DD7AA7C15EC20FD00C67AE1 /* CADispatchQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CADispatchQueue.h; sourceTree = "<group>"; };
2DD7AA7F15EC3DB800C67AE1 /* CACFObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CACFObject.h; sourceTree = "<group>"; };
2DD7AA9515EC551500C67AE1 /* SA_Device.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SA_Device.cpp; sourceTree = "<group>"; };
2DD7AA9615EC551600C67AE1 /* SA_Device.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SA_Device.h; sourceTree = "<group>"; };
2DD7AA9915EC572000C67AE1 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
2DED182415C356BA0091BE97 /* SimpleAudioDriver-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleAudioDriver-Info.plist"; sourceTree = "<group>"; };
2DED182515C356BA0091BE97 /* SimpleAudioDriver.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SimpleAudioDriver.cpp; sourceTree = "<group>"; };
2DED182615C356BA0091BE97 /* SimpleAudioDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleAudioDriver.h; sourceTree = "<group>"; };
2DED182915C356BA0091BE97 /* SimpleAudioDriverUserClient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SimpleAudioDriverUserClient.cpp; sourceTree = "<group>"; };
2DED182A15C356BA0091BE97 /* SimpleAudioDriverUserClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleAudioDriverUserClient.h; sourceTree = "<group>"; };
2DED182B15C356BA0091BE97 /* SimpleAudioDriverTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleAudioDriverTypes.h; sourceTree = "<group>"; };
2DED183815C357180091BE97 /* SimpleAudioDriver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleAudioDriver.kext; sourceTree = BUILT_PRODUCTS_DIR; };
2DED183A15C357180091BE97 /* Kernel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kernel.framework; path = System/Library/Frameworks/Kernel.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2D7477A61578168D00412279 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2D7477AD1578168D00412279 /* CoreFoundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D76D95D15E48B2000FF0F33 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2D76D96115E48B2000FF0F33 /* CoreFoundation.framework in Frameworks */,
2DD7AA9A15EC572000C67AE1 /* IOKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2DED183315C357180091BE97 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2D616EF015B8C82500D598BD /* NullAudio */ = {
isa = PBXGroup;
children = (
2D46CA7C17D6AF4200049D4A /* DeviceIcon.icns */,
2D46CA7817D6AD8A00049D4A /* Localizable.strings */,
2D616EF115B8C82500D598BD /* NullAudio-Info.plist */,
2D616EF215B8C82500D598BD /* NullAudio.c */,
);
path = NullAudio;
sourceTree = "<group>";
};
2D74779B1578162B00412279 = {
isa = PBXGroup;
children = (
2DA8FA1515FEAAB000F04B50 /* ReadMe.txt */,
2D616EF015B8C82500D598BD /* NullAudio */,
2DED182215C356BA0091BE97 /* SimpleAudio */,
2DD7AA0D15EAFD3300C67AE1 /* PublicUtility */,
2D7477AB1578168D00412279 /* Frameworks */,
2D7477AA1578168D00412279 /* Products */,
);
sourceTree = "<group>";
};
2D7477AA1578168D00412279 /* Products */ = {
isa = PBXGroup;
children = (
2D7477A91578168D00412279 /* NullAudio.driver */,
2DED183815C357180091BE97 /* SimpleAudioDriver.kext */,
2D76D96015E48B2000FF0F33 /* SimpleAudioPlugIn.driver */,
);
name = Products;
sourceTree = "<group>";
};
2D7477AB1578168D00412279 /* Frameworks */ = {
isa = PBXGroup;
children = (
2D7477EC157823CF00412279 /* CoreAudio.framework */,
2D7477AC1578168D00412279 /* CoreFoundation.framework */,
2DD7AA9915EC572000C67AE1 /* IOKit.framework */,
2DED183A15C357180091BE97 /* Kernel.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
2DA8FA7E15FEC6B500F04B50 /* Resources */ = {
isa = PBXGroup;
children = (
2D47CAA215FEC82B002AAFB5 /* Localizable.strings */,
);
path = Resources;
sourceTree = "<group>";
};
2DD7AA0D15EAFD3300C67AE1 /* PublicUtility */ = {
isa = PBXGroup;
children = (
2DD7AA0E15EAFD3300C67AE1 /* CACFArray.cpp */,
2DD7AA0F15EAFD3300C67AE1 /* CACFArray.h */,
2DD7AA1015EAFD3300C67AE1 /* CACFDictionary.cpp */,
2DD7AA1115EAFD3300C67AE1 /* CACFDictionary.h */,
2DD7AA1215EAFD3300C67AE1 /* CACFNumber.cpp */,
2DD7AA1315EAFD3300C67AE1 /* CACFNumber.h */,
2DD7AA7F15EC3DB800C67AE1 /* CACFObject.h */,
2DD7AA1415EAFD3300C67AE1 /* CACFString.cpp */,
2DD7AA1515EAFD3300C67AE1 /* CACFString.h */,
2DD7AA1615EAFD3300C67AE1 /* CADebugger.cpp */,
2DD7AA1715EAFD3300C67AE1 /* CADebugger.h */,
2DD7AA1815EAFD3300C67AE1 /* CADebugMacros.cpp */,
2DD7AA1915EAFD3300C67AE1 /* CADebugMacros.h */,
2DD7AA1A15EAFD3300C67AE1 /* CADebugPrintf.cpp */,
2DD7AA1B15EAFD3300C67AE1 /* CADebugPrintf.h */,
2DD7AA7B15EC20FD00C67AE1 /* CADispatchQueue.cpp */,
2DD7AA7C15EC20FD00C67AE1 /* CADispatchQueue.h */,
2DD7AA1C15EAFD3300C67AE1 /* CAException.h */,
2DD7AA1D15EAFD3300C67AE1 /* CAGuard.cpp */,
2DD7AA1E15EAFD3300C67AE1 /* CAGuard.h */,
2DD7AA1F15EAFD3300C67AE1 /* CAHostTimeBase.cpp */,
2DD7AA2015EAFD3300C67AE1 /* CAHostTimeBase.h */,
2DD7AA2115EAFD3300C67AE1 /* CAMutex.cpp */,
2DD7AA2215EAFD3300C67AE1 /* CAMutex.h */,
2D4DE41215EDF8D500E96F0D /* CAVolumeCurve.cpp */,
2D4DE41315EDF8D500E96F0D /* CAVolumeCurve.h */,
);
path = PublicUtility;
sourceTree = "<group>";
};
2DED182215C356BA0091BE97 /* SimpleAudio */ = {
isa = PBXGroup;
children = (
2DED182315C356BA0091BE97 /* Driver */,
2DED182C15C356BA0091BE97 /* Plug-In */,
);
path = SimpleAudio;
sourceTree = "<group>";
};
2DED182315C356BA0091BE97 /* Driver */ = {
isa = PBXGroup;
children = (
2DED182415C356BA0091BE97 /* SimpleAudioDriver-Info.plist */,
2DED182515C356BA0091BE97 /* SimpleAudioDriver.cpp */,
2DED182615C356BA0091BE97 /* SimpleAudioDriver.h */,
2DED182915C356BA0091BE97 /* SimpleAudioDriverUserClient.cpp */,
2DED182A15C356BA0091BE97 /* SimpleAudioDriverUserClient.h */,
2DED182B15C356BA0091BE97 /* SimpleAudioDriverTypes.h */,
);
path = Driver;
sourceTree = "<group>";
};
2DED182C15C356BA0091BE97 /* Plug-In */ = {
isa = PBXGroup;
children = (
2DD7AA9515EC551500C67AE1 /* SA_Device.cpp */,
2DD7AA9615EC551600C67AE1 /* SA_Device.h */,
2DD7AA0815EACDDF00C67AE1 /* SA_IOKit.cpp */,
2DD7AA0915EACDDF00C67AE1 /* SA_IOKit.h */,
2D76D97815E498EB00FF0F33 /* SA_Object.cpp */,
2D76D97915E498EB00FF0F33 /* SA_Object.h */,
2D76D98B15E56E4E00FF0F33 /* SA_PlugIn-Info.plist */,
2D76D97415E48B6400FF0F33 /* SA_PlugIn.cpp */,
2D76D97515E48B6400FF0F33 /* SA_PlugIn.h */,
2DA8FA7E15FEC6B500F04B50 /* Resources */,
);
path = "Plug-In";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
2D76D95E15E48B2000FF0F33 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
2D76D97715E48B6400FF0F33 /* SA_PlugIn.h in Headers */,
2D76D97B15E498EB00FF0F33 /* SA_Object.h in Headers */,
2DD7AA0B15EACDE000C67AE1 /* SA_IOKit.h in Headers */,
2DD7AA2415EAFD5100C67AE1 /* CACFArray.h in Headers */,
2DD7AA2615EAFD5100C67AE1 /* CACFDictionary.h in Headers */,
2DD7AA2815EAFD5100C67AE1 /* CACFNumber.h in Headers */,
2DD7AA2A15EAFD5100C67AE1 /* CACFString.h in Headers */,
2DD7AA2C15EAFD5100C67AE1 /* CADebugger.h in Headers */,
2DD7AA2E15EAFD5100C67AE1 /* CADebugMacros.h in Headers */,
2DD7AA3015EAFD5100C67AE1 /* CADebugPrintf.h in Headers */,
2DD7AA3115EAFD5100C67AE1 /* CAException.h in Headers */,
2DD7AA3315EAFD5100C67AE1 /* CAGuard.h in Headers */,
2DD7AA3515EAFD5100C67AE1 /* CAHostTimeBase.h in Headers */,
2DD7AA3715EAFD5100C67AE1 /* CAMutex.h in Headers */,
2DD7AA7E15EC20FD00C67AE1 /* CADispatchQueue.h in Headers */,
2DD7AA8015EC3DB800C67AE1 /* CACFObject.h in Headers */,
2DD7AA9815EC551600C67AE1 /* SA_Device.h in Headers */,
2D4DE41515EDF8D500E96F0D /* CAVolumeCurve.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2DED183415C357180091BE97 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
2DED184A15C359AC0091BE97 /* SimpleAudioDriver.h in Headers */,
2DED184C15C359AC0091BE97 /* SimpleAudioDriverUserClient.h in Headers */,
2DED184D15C359AC0091BE97 /* SimpleAudioDriverTypes.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
2D7477A81578168D00412279 /* NullAudio */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D7477B51578168D00412279 /* Build configuration list for PBXNativeTarget "NullAudio" */;
buildPhases = (
2D7477A51578168D00412279 /* Sources */,
2D7477A61578168D00412279 /* Frameworks */,
2D46CA7A17D6ADC500049D4A /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = NullAudio;
productName = AudioNULLDriver;
productReference = 2D7477A91578168D00412279 /* NullAudio.driver */;
productType = "com.apple.product-type.bundle";
};
2D76D95F15E48B2000FF0F33 /* SimpleAudioPlugIn */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D76D97215E48B2000FF0F33 /* Build configuration list for PBXNativeTarget "SimpleAudioPlugIn" */;
buildPhases = (
2D76D95E15E48B2000FF0F33 /* Headers */,
2D76D95C15E48B2000FF0F33 /* Sources */,
2D76D95D15E48B2000FF0F33 /* Frameworks */,
2DA8FA4B15FEABE000F04B50 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = SimpleAudioPlugIn;
productName = SimpleAudioPlugIn;
productReference = 2D76D96015E48B2000FF0F33 /* SimpleAudioPlugIn.driver */;
productType = "com.apple.product-type.bundle";
};
2DED183715C357180091BE97 /* SimpleAudioDriver */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2DED184515C357180091BE97 /* Build configuration list for PBXNativeTarget "SimpleAudioDriver" */;
buildPhases = (
2DED183415C357180091BE97 /* Headers */,
2DED183215C357180091BE97 /* Sources */,
2DED183315C357180091BE97 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = SimpleAudioDriver;
productName = SimpleAudioDriver;
productReference = 2DED183815C357180091BE97 /* SimpleAudioDriver.kext */;
productType = "com.apple.product-type.kernel-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2D74779D1578162B00412279 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0500;
};
buildConfigurationList = 2D7477A01578162B00412279 /* Build configuration list for PBXProject "AudioDriverExamples" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
English,
);
mainGroup = 2D74779B1578162B00412279;
productRefGroup = 2D7477AA1578168D00412279 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2D7477A81578168D00412279 /* NullAudio */,
2DED183715C357180091BE97 /* SimpleAudioDriver */,
2D76D95F15E48B2000FF0F33 /* SimpleAudioPlugIn */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2D46CA7A17D6ADC500049D4A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D46CA7B17D6ADCA00049D4A /* Localizable.strings in Resources */,
2D46CA7D17D6AF4200049D4A /* DeviceIcon.icns in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2DA8FA4B15FEABE000F04B50 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D47CAA415FEC82B002AAFB5 /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2D7477A51578168D00412279 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D616EF415B8C82500D598BD /* NullAudio.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D76D95C15E48B2000FF0F33 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D76D97615E48B6400FF0F33 /* SA_PlugIn.cpp in Sources */,
2D76D97A15E498EB00FF0F33 /* SA_Object.cpp in Sources */,
2DD7AA0A15EACDE000C67AE1 /* SA_IOKit.cpp in Sources */,
2DD7AA2315EAFD5100C67AE1 /* CACFArray.cpp in Sources */,
2DD7AA2515EAFD5100C67AE1 /* CACFDictionary.cpp in Sources */,
2DD7AA2715EAFD5100C67AE1 /* CACFNumber.cpp in Sources */,
2DD7AA2915EAFD5100C67AE1 /* CACFString.cpp in Sources */,
2DD7AA2B15EAFD5100C67AE1 /* CADebugger.cpp in Sources */,
2DD7AA2D15EAFD5100C67AE1 /* CADebugMacros.cpp in Sources */,
2DD7AA2F15EAFD5100C67AE1 /* CADebugPrintf.cpp in Sources */,
2DD7AA3215EAFD5100C67AE1 /* CAGuard.cpp in Sources */,
2DD7AA3415EAFD5100C67AE1 /* CAHostTimeBase.cpp in Sources */,
2DD7AA3615EAFD5100C67AE1 /* CAMutex.cpp in Sources */,
2DD7AA7D15EC20FD00C67AE1 /* CADispatchQueue.cpp in Sources */,
2DD7AA9715EC551600C67AE1 /* SA_Device.cpp in Sources */,
2D4DE41415EDF8D500E96F0D /* CAVolumeCurve.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2DED183215C357180091BE97 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2DED184915C359AC0091BE97 /* SimpleAudioDriver.cpp in Sources */,
2DED184B15C359AC0091BE97 /* SimpleAudioDriverUserClient.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
2D46CA7817D6AD8A00049D4A /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
2D46CA7917D6AD8A00049D4A /* English */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
2D47CAA215FEC82B002AAFB5 /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
2D47CAA315FEC82B002AAFB5 /* English */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
2D7477A21578162B00412279 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_OPTIMIZATION_LEVEL = s;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SDKROOT = macosx;
};
name = Release;
};
2D7477A31578162B00412279 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SDKROOT = macosx;
};
name = Debug;
};
2D7477A41578164E00412279 /* Debug-Opt */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_64_BIT)";
CLANG_CXX_LANGUAGE_STANDARD = "c++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_OPTIMIZATION_LEVEL = s;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
SDKROOT = macosx;
};
name = "Debug-Opt";
};
2D7477B61578168D00412279 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=0",
);
INFOPLIST_FILE = "NullAudio/NullAudio-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = Release;
};
2D7477B71578168D00412279 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
);
INFOPLIST_FILE = "NullAudio/NullAudio-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = Debug;
};
2D7477B81578168D00412279 /* Debug-Opt */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
);
INFOPLIST_FILE = "NullAudio/NullAudio-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = "Debug-Opt";
};
2D76D96F15E48B2000FF0F33 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_ENABLE_CPP_RTTI = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"CoreAudio_Debug=0",
"CoreAudio_UseSysLog=1",
);
INFOPLIST_FILE = "SimpleAudio/Plug-In/SA_PlugIn-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = Release;
};
2D76D97015E48B2000FF0F33 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_ENABLE_CPP_RTTI = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=1",
);
INFOPLIST_FILE = "SimpleAudio/Plug-In/SA_PlugIn-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = Debug;
};
2D76D97115E48B2000FF0F33 /* Debug-Opt */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_ENABLE_CPP_RTTI = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=1",
);
INFOPLIST_FILE = "SimpleAudio/Plug-In/SA_PlugIn-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = driver;
};
name = "Debug-Opt";
};
2DED184615C357180091BE97 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
GCC_PREPROCESSOR_DEFINITIONS = "";
INFOPLIST_FILE = "SimpleAudio/Driver/SimpleAudioDriver-Info.plist";
INSTALL_PATH = "$(SYSTEM_LIBRARY_DIR)/Extensions";
MODULE_NAME = com.apple.audio.SimpleAudioDriver;
MODULE_VERSION = 1.0;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = kext;
};
name = Release;
};
2DED184715C357180091BE97 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
INFOPLIST_FILE = "SimpleAudio/Driver/SimpleAudioDriver-Info.plist";
INSTALL_PATH = "$(SYSTEM_LIBRARY_DIR)/Extensions";
MODULE_NAME = com.apple.audio.SimpleAudioDriver;
MODULE_VERSION = 1.0;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = kext;
};
name = Debug;
};
2DED184815C357180091BE97 /* Debug-Opt */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
INFOPLIST_FILE = "SimpleAudio/Driver/SimpleAudioDriver-Info.plist";
INSTALL_PATH = "$(SYSTEM_LIBRARY_DIR)/Extensions";
MODULE_NAME = com.apple.audio.SimpleAudioDriver;
MODULE_VERSION = 1.0;
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = kext;
};
name = "Debug-Opt";
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2D7477A01578162B00412279 /* Build configuration list for PBXProject "AudioDriverExamples" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D7477A21578162B00412279 /* Release */,
2D7477A31578162B00412279 /* Debug */,
2D7477A41578164E00412279 /* Debug-Opt */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D7477B51578168D00412279 /* Build configuration list for PBXNativeTarget "NullAudio" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D7477B61578168D00412279 /* Release */,
2D7477B71578168D00412279 /* Debug */,
2D7477B81578168D00412279 /* Debug-Opt */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D76D97215E48B2000FF0F33 /* Build configuration list for PBXNativeTarget "SimpleAudioPlugIn" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D76D96F15E48B2000FF0F33 /* Release */,
2D76D97015E48B2000FF0F33 /* Debug */,
2D76D97115E48B2000FF0F33 /* Debug-Opt */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2DED184515C357180091BE97 /* Build configuration list for PBXNativeTarget "SimpleAudioDriver" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2DED184615C357180091BE97 /* Release */,
2DED184715C357180091BE97 /* Debug */,
2DED184815C357180091BE97 /* Debug-Opt */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 2D74779D1578162B00412279 /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.apple.audio.${PRODUCT_NAME:rfc1034identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFPlugInFactories</key>
<dict>
<key>99A15A8B-DA3C-42C3-BD5D-D035A0C2377A</key>
<string>NullAudio_Create</string>
</dict>
<key>CFPlugInTypes</key>
<dict>
<key>443ABAB8-E7B3-491A-B985-BEB9187030DB</key>
<array>
<string>99A15A8B-DA3C-42C3-BD5D-D035A0C2377A</string>
</array>
</dict>
</dict>
</plist>
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
TravisCI's OS X VMs don't have any audio devices installed by default, so we
install Apple's NullAudio sample driver before the tests run. Otherwise the
tests would fail because BGMApp currently crashes on launch if you don't have
any audio devices.
@@ -526,6 +526,7 @@
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
DEAD_CODE_STRIPPING = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
@@ -535,6 +536,7 @@
"CoreAudio_Debug=1",
"CoreAudio_UseSysLog=1",
"CoreAudio_StopOnAssert=1",
"CoreAudio_ThreadStampMessages=0",
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -542,6 +544,9 @@
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
@@ -560,6 +565,7 @@
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
DEAD_CODE_STRIPPING = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = s;
@@ -568,6 +574,7 @@
"CoreAudio_Debug=0",
"CoreAudio_UseSysLog=1",
"CoreAudio_StopOnAssert=0",
"CoreAudio_ThreadStampMessages=0",
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -575,6 +582,9 @@
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
GCC_WARN_SHADOW = YES;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
+100 -60
View File
@@ -20,8 +20,8 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Device.cpp from Apple's SimpleAudioPlugin sample code. Also uses a few sections from Apple's NullAudio.c
// sample code (found in the same sample project).
// Based largely on SA_Device.cpp from Apple's SimpleAudioDriver Plug-In sample code. Also uses a few sections from Apple's
// NullAudio.c sample code (found in the same sample project).
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
@@ -215,6 +215,8 @@ UInt32 BGM_Device::GetPropertyDataSize(AudioObjectID inObjectID, pid_t inClientP
void BGM_Device::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, UInt32& outDataSize, void* outData) const
{
ThrowIfNULL(outData, BGM_RuntimeException(), "BGM_Device::GetPropertyData: !outData");
if(inObjectID == mObjectID)
{
Device_GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);
@@ -235,6 +237,8 @@ void BGM_Device::GetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, co
void BGM_Device::SetPropertyData(AudioObjectID inObjectID, pid_t inClientPID, const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
{
ThrowIfNULL(inData, BGM_RuntimeException(), "BGM_Device::SetPropertyData: no data");
if(inObjectID == mObjectID)
{
Device_SetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, inData);
@@ -286,6 +290,7 @@ bool BGM_Device::Device_HasProperty(AudioObjectID inObjectID, pid_t inClientPID,
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:
case kAudioDeviceCustomPropertyAppVolumes:
case kAudioDeviceCustomPropertyClients:
theAnswer = true;
break;
@@ -338,6 +343,7 @@ bool BGM_Device::Device_IsPropertySettable(AudioObjectID inObjectID, pid_t inCli
case kAudioObjectPropertyCustomPropertyInfoList:
case kAudioDeviceCustomPropertyDeviceAudibleState:
case kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp:
case kAudioDeviceCustomPropertyClients:
theAnswer = false;
break;
@@ -483,7 +489,7 @@ UInt32 BGM_Device::Device_GetPropertyDataSize(AudioObjectID inObjectID, pid_t in
break;
case kAudioObjectPropertyCustomPropertyInfoList:
theAnswer = sizeof(AudioServerPlugInCustomPropertyInfo) * 5;
theAnswer = sizeof(AudioServerPlugInCustomPropertyInfo) * 6;
break;
case kAudioDeviceCustomPropertyDeviceAudibleState:
@@ -503,7 +509,11 @@ UInt32 BGM_Device::Device_GetPropertyDataSize(AudioObjectID inObjectID, pid_t in
break;
case kAudioDeviceCustomPropertyAppVolumes:
theAnswer = sizeof(CFPropertyListRef);
theAnswer = sizeof(CFArrayRef);
break;
case kAudioDeviceCustomPropertyClients:
theAnswer = sizeof(CFArrayRef);
break;
default:
@@ -848,8 +858,7 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
// The sample rate is protected by the state lock
CAMutex::Locker theStateLocker(mStateMutex);
// need to lock around fetching the sample rate
*reinterpret_cast<Float64*>(outData) = _HW_GetSampleRate();
outDataSize = sizeof(Float64);
}
@@ -877,8 +886,14 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
// fill out the return array
if(theNumberItemsToFetch > 0)
{
((AudioValueRange*)outData)[0].mMinimum = 0.0;
((AudioValueRange*)outData)[0].mMaximum = DBL_MAX;
// 0 would cause divide-by-zero errors in other BGM_Device functions (and
// wouldn't make sense anyway).
((AudioValueRange*)outData)[0].mMinimum = 1.0;
// Just in case DBL_MAX would cause problems in a client for some reason,
// use an arbitrary very large number instead. (It wouldn't make sense to
// actually set the sample rate this high, but I don't know what a
// reasonable maximum would be.)
((AudioValueRange*)outData)[0].mMaximum = 1000000000.0;
}
// report how much we wrote
@@ -893,7 +908,7 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
break;
case kAudioDevicePropertyPreferredChannelsForStereo:
// This property returns which two channesl to use as left/right for stereo
// This property returns which two channels to use as left/right for stereo
// data by default. Note that the channel numbers are 1-based.
ThrowIf(inDataSize < (2 * sizeof(UInt32)), CAException(kAudioHardwareBadPropertySizeError), "BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDevicePropertyPreferredChannelsForStereo for the device");
((UInt32*)outData)[0] = 1;
@@ -950,9 +965,9 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
theNumberItemsToFetch = inDataSize / sizeof(AudioServerPlugInCustomPropertyInfo);
// clamp it to the number of items we have
if(theNumberItemsToFetch > 5)
if(theNumberItemsToFetch > 6)
{
theNumberItemsToFetch = 5;
theNumberItemsToFetch = 6;
}
if(theNumberItemsToFetch > 0)
@@ -985,6 +1000,12 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
((AudioServerPlugInCustomPropertyInfo*)outData)[4].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;
((AudioServerPlugInCustomPropertyInfo*)outData)[4].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;
}
if(theNumberItemsToFetch > 5)
{
((AudioServerPlugInCustomPropertyInfo*)outData)[5].mSelector = kAudioDeviceCustomPropertyClients;
((AudioServerPlugInCustomPropertyInfo*)outData)[5].mPropertyDataType = kAudioServerPlugInCustomPropertyDataTypeCFPropertyList;
((AudioServerPlugInCustomPropertyInfo*)outData)[5].mQualifierDataType = kAudioServerPlugInCustomPropertyDataTypeNone;
}
outDataSize = theNumberItemsToFetch * sizeof(AudioServerPlugInCustomPropertyInfo);
break;
@@ -1033,6 +1054,15 @@ void BGM_Device::Device_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
outDataSize = sizeof(CFArrayRef);
}
break;
case kAudioDeviceCustomPropertyClients:
{
ThrowIf(inDataSize < sizeof(CFArrayRef), CAException(kAudioHardwareBadPropertySizeError), "BGM_Device::Device_GetPropertyData: not enough space for the return value of kAudioDeviceCustomPropertyClients for the device");
CAMutex::Locker theStateLocker(mStateMutex);
*reinterpret_cast<CFArrayRef*>(outData) = mClients.CopyClientPIDsAndBundleIDs().GetCFArray();
outDataSize = sizeof(CFArrayRef);
}
break;
default:
BGM_Object::GetPropertyData(inObjectID, inClientPID, inAddress, inQualifierDataSize, inQualifierData, inDataSize, outDataSize, outData);
@@ -1049,6 +1079,7 @@ void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
ThrowIf(inDataSize < sizeof(Float64), CAException(kAudioHardwareBadPropertySizeError), "BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDevicePropertyNominalSampleRate");
// BGMDevice's input and output sample rates are the same, so we just pass the request to Stream_SetPropertyData for one of them
// TODO: Set the property for both streams just in case that assumption changes? (As unlikely as that is.)
Float64 theNewSampleRate = *reinterpret_cast<const Float64*>(inData);
@@ -1062,7 +1093,7 @@ void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
theStreamFormat.mSampleRate = theNewSampleRate;
Stream_SetPropertyData(inObjectID, inClientPID, theStreamPropertyAddress, 0, NULL, theStreamFormatDataSize, &theStreamFormat);
Stream_SetPropertyData(kObjectID_Stream_Input, inClientPID, theStreamPropertyAddress, 0, NULL, theStreamFormatDataSize, &theStreamFormat);
}
break;
@@ -1111,15 +1142,17 @@ void BGM_Device::Device_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
{
ThrowIf(inDataSize < sizeof(CFStringRef), CAException(kAudioHardwareBadPropertySizeError), "BGM_Device::Device_SetPropertyData: wrong size for the data for kAudioDeviceCustomPropertyMusicPlayerBundleID");
CFStringRef theBundleIDRef = *reinterpret_cast<const CFStringRef*>(inData);
ThrowIfNULL(theBundleIDRef, CAException(kAudioHardwareIllegalOperationError), "BGM_Device::Device_SetPropertyData: kAudioDeviceCustomPropertyMusicPlayerBundleID cannot be set to NULL");
ThrowIf(CFGetTypeID(theBundleIDRef) != CFStringGetTypeID(), CAException(kAudioHardwareIllegalOperationError), "BGM_Device::Device_SetPropertyData: CFType given for kAudioDeviceCustomPropertyMusicPlayerBundleID was not a CFString");
CAMutex::Locker theStateLocker(mStateMutex);
CFRetain(theBundleIDRef);
CACFString bundleID(theBundleIDRef);
bool propertyWasChanged = mClients.SetMusicPlayer(bundleID);
if(propertyWasChanged)
@@ -1410,8 +1443,9 @@ void BGM_Device::Stream_GetPropertyData(AudioObjectID inObjectID, pid_t inClient
((AudioStreamRangedDescription*)outData)[0].mFormat.mBytesPerFrame = 8;
((AudioStreamRangedDescription*)outData)[0].mFormat.mChannelsPerFrame = 2;
((AudioStreamRangedDescription*)outData)[0].mFormat.mBitsPerChannel = 32;
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMinimum = 0.0;
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMaximum = DBL_MAX;
// These match kAudioDevicePropertyAvailableNominalSampleRates.
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMinimum = 1.0;
((AudioStreamRangedDescription*)outData)[0].mSampleRateRange.mMaximum = 1000000000.0;
}
// report how much we wrote
@@ -1474,7 +1508,7 @@ void BGM_Device::Stream_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
ThrowIf(theNewFormat->mBytesPerFrame != 8, CAException(kAudioDeviceUnsupportedFormatError), "BGM_Device::Stream_SetPropertyData: unsupported bytes per frame for kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mChannelsPerFrame != 2, CAException(kAudioDeviceUnsupportedFormatError), "BGM_Device::Stream_SetPropertyData: unsupported channels per frame for kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mBitsPerChannel != 32, CAException(kAudioDeviceUnsupportedFormatError), "BGM_Device::Stream_SetPropertyData: unsupported bits per channel for kAudioStreamPropertyPhysicalFormat");
ThrowIf(theNewFormat->mSampleRate < 0.0, //(theNewFormat->mSampleRate != 44100.0) && (theNewFormat->mSampleRate != 48000.0),
ThrowIf(theNewFormat->mSampleRate < 1.0, //(theNewFormat->mSampleRate != 44100.0) && (theNewFormat->mSampleRate != 48000.0),
CAException(kAudioDeviceUnsupportedFormatError),
"BGM_Device::Stream_SetPropertyData: unsupported sample rate for kAudioStreamPropertyPhysicalFormat");
@@ -1483,7 +1517,6 @@ void BGM_Device::Stream_SetPropertyData(AudioObjectID inObjectID, pid_t inClient
CAMutex::Locker theStateLocker(mStateMutex);
Float64 theOldSampleRate = _HW_GetSampleRate();
// make sure that the new value is different than the old value
if(theNewSampleRate != theOldSampleRate)
{
mPendingSampleRate = theNewSampleRate;
@@ -1815,51 +1848,58 @@ void BGM_Device::Control_SetPropertyData(AudioObjectID inObjectID, pid_t inClien
void BGM_Device::StartIO(UInt32 inClientID)
{
CAMutex::Locker theStateLocker(mStateMutex);
bool clientIsBGMApp, bgmAppHasClientRegistered;
// An overview of the process this function is part of:
// - A client starts IO.
// - The plugin host (the HAL) calls the StartIO function in BGM_PluginInterface, which calls this function.
// - BGMDriver sends a message to BGMApp telling it to start the (real) audio hardware.
// - BGMApp starts the hardware and, after the hardware is ready, replies to BGMDriver's message.
// - BGMDriver lets the host know that it's ready to do IO by returning from StartIO.
// Update our client data.
//
// We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation also
// add this task to the queue and the updates should be done in order.
bool didStartIO = mTaskQueue.QueueSync_StartClientIO(&mClients, inClientID);
// We only tell the hardware to start if this is the first time IO has been started
if(didStartIO)
{
kern_return_t theError = _HW_StartIO();
ThrowIfKernelError(theError,
CAException(theError),
"BGM_Device::StartIO: Failed to start because of an error calling down to the driver.");
{
CAMutex::Locker theStateLocker(mStateMutex);
// We only return from StartIO after BGMApp is ready to pass the audio through to the output device. That way
// the HAL doesn't start sending us data before BGMApp can play it, which would mean we'd have to either drop
// frames or increase latency.
if(!mClients.IsBGMApp(inClientID) && mClients.BGMAppHasClientRegistered())
// An overview of the process this function is part of:
// - A client starts IO.
// - The plugin host (the HAL) calls the StartIO function in BGM_PlugInInterface, which calls this function.
// - BGMDriver sends a message to BGMApp telling it to start the (real) audio hardware.
// - BGMApp starts the hardware and, after the hardware is ready, replies to BGMDriver's message.
// - BGMDriver lets the host know that it's ready to do IO by returning from StartIO.
// Update our client data.
//
// We add the work to the task queue, rather than doing it here, because BeginIOOperation and EndIOOperation
// also add this task to the queue and the updates should be done in order.
bool didStartIO = mTaskQueue.QueueSync_StartClientIO(&mClients, inClientID);
// We only tell the hardware to start if this is the first time IO has been started.
if(didStartIO)
{
UInt64 theXPCError = WaitForBGMAppToStartOutputDevice();
if(theXPCError == kBGMXPC_Success)
{
DebugMsg("BGM_Device::StartIO: Ready for IO.");
}
else if(theXPCError == kBGMXPC_MessageFailure)
{
// This most likely means BGMXPCHelper isn't installed or has crashed. IO will probably still work,
// but we may drop frames while the audio hardware starts up.
DebugMsg("BGM_Device::StartIO: Couldn't reach BGMApp via XPC. Attempting to start IO anyway.");
}
else
{
DebugMsg("BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu", theXPCError);
Throw(CAException(kAudioHardwareUnspecifiedError));
}
kern_return_t theError = _HW_StartIO();
ThrowIfKernelError(theError,
CAException(theError),
"BGM_Device::StartIO: Failed to start because of an error calling down to the driver.");
}
clientIsBGMApp = mClients.IsBGMApp(inClientID);
bgmAppHasClientRegistered = mClients.BGMAppHasClientRegistered();
}
// We only return from StartIO after BGMApp is ready to pass the audio through to the output device. That way
// the HAL doesn't start sending us data before BGMApp can play it, which would mean we'd have to either drop
// frames or increase latency.
if(!clientIsBGMApp && bgmAppHasClientRegistered)
{
UInt64 theXPCError = WaitForBGMAppToStartOutputDevice();
if(theXPCError == kBGMXPC_Success)
{
DebugMsg("BGM_Device::StartIO: Ready for IO.");
}
else if(theXPCError == kBGMXPC_MessageFailure)
{
// This most likely means BGMXPCHelper isn't installed or has crashed. IO will probably still work,
// but we may drop frames while the audio hardware starts up.
DebugMsg("BGM_Device::StartIO: Couldn't reach BGMApp via XPC. Attempting to start IO anyway.");
}
else
{
DebugMsg("BGM_Device::StartIO: BGMApp failed to start the output device. theXPCError=%llu", theXPCError);
Throw(CAException(kAudioHardwareUnspecifiedError));
}
}
}
@@ -2268,7 +2308,7 @@ SInt32 BGM_Device::_HW_GetVolumeControlValue(int inObjectID) const
return mOutputMasterVolumeControlRawValueShadow;
};
throw (CAException(kAudioHardwareBadObjectError));
Throw(CAException(kAudioHardwareBadObjectError));
}
kern_return_t BGM_Device::_HW_SetVolumeControlValue(int inObjectID, SInt32 inNewControlValue)
+1 -1
View File
@@ -20,7 +20,7 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Device.h from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_Device.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
+1 -1
View File
@@ -20,7 +20,7 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Object.cpp from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_Object.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
// Similarly to BGM_Object.h, this file hasn't been changed much from SA_Object.cpp, except to
+2 -2
View File
@@ -20,10 +20,10 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_Object.h from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_Object.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
// The base class for our classes that represent audio objects. (See AudioServerPlugin.h for a
// The base class for our classes that represent audio objects. (See AudioServerPlugIn.h for a
// quick explanation of audio objects.)
//
// This is a stripped down version of SA_Object.h. Unlike the sample code plugin that SA_Object.h
+1 -1
View File
@@ -20,7 +20,7 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
+1 -1
View File
@@ -20,7 +20,7 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.h from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_PlugIn.h from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
+3 -2
View File
@@ -20,7 +20,7 @@
// Copyright © 2016 Kyle Neideck
// Portions copyright (C) 2013 Apple Inc. All Rights Reserved.
//
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioPlugin sample code.
// Based largely on SA_PlugIn.cpp from Apple's SimpleAudioDriver Plug-In sample code.
// https://developer.apple.com/library/mac/samplecode/AudioDriverExamples
//
@@ -561,7 +561,8 @@ static OSStatus BGM_SetPropertyData(AudioServerPlugInDriverRef inDriver, AudioOb
{
// check the arguments
ThrowIf(inDriver != gAudioServerPlugInDriverRef, CAException(kAudioHardwareBadObjectError), "BGM_SetPropertyData: bad driver reference");
ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "BGM_SetPropertyData: no address");
ThrowIfNULL(inAddress, CAException(kAudioHardwareIllegalOperationError), "BGM_SetPropertyData: no address");
ThrowIfNULL(inData, CAException(kAudioHardwareIllegalOperationError), "BGM_SetPropertyData: no data");
BGM_Object& theAudioObject = BGM_LookUpOwnerObject(inObjectID);
if(theAudioObject.HasProperty(inObjectID, inClientProcessID, *inAddress))
+50 -11
View File
@@ -34,6 +34,7 @@
// PublicUtility Includes
#include "CAException.h"
#include "CADebugMacros.h"
#include "CAAtomic.h"
// System Includes
#include <mach/mach_init.h>
@@ -80,7 +81,7 @@ BGM_TaskQueue::BGM_TaskQueue()
// Pre-allocate enough tasks in mNonRealTimeThreadTasksFreeList that the real-time threads should never have to
// allocate memory when adding a task to the non-realtime queue.
for(int i = 0; i < kNonRealTimeThreadTaskBufferSize; i++)
for(UInt32 i = 0; i < kNonRealTimeThreadTaskBufferSize; i++)
{
BGM_Task* theTask = new BGM_Task;
mNonRealTimeThreadTasksFreeList.push_NA(theTask);
@@ -207,6 +208,7 @@ UInt64 BGM_TaskQueue::QueueSync(BGM_TaskID inTaskID, bool inRunOnRealtimeThre
//
// The worker thread signals all threads waiting on this semaphore when it finishes a task. The comments in WorkerThreadProc
// explain why we have to check the condition in a loop here.
bool didLogTimeoutMessage = false;
while(!theTask.IsComplete())
{
semaphore_t theTaskCompletedSemaphore =
@@ -216,10 +218,25 @@ UInt64 BGM_TaskQueue::QueueSync(BGM_TaskID inTaskID, bool inRunOnRealtimeThre
theError = semaphore_timedwait(theTaskCompletedSemaphore,
(mach_timespec_t){ 0, kRealTimeThreadMaximumComputationNs * 4 });
if(theError != KERN_OPERATION_TIMED_OUT)
if(theError == KERN_OPERATION_TIMED_OUT)
{
if(!didLogTimeoutMessage && inRunOnRealtimeThread)
{
DebugMsg("BGM_TaskQueue::QueueSync: Task %d taking longer than expected.", theTask.GetTaskID());
didLogTimeoutMessage = true;
}
}
else
{
BGM_Utils::ThrowIfMachError("BGM_TaskQueue::QueueSync", "semaphore_timedwait", theError);
}
CAMemoryBarrier();
}
if(didLogTimeoutMessage)
{
DebugMsg("BGM_TaskQueue::QueueSync: Late task %d finished.", theTask.GetTaskID());
}
if(theTask.GetReturnValue() != INT64_MAX)
@@ -299,9 +316,9 @@ void* __nullable BGM_TaskQueue::NonRealTimeThreadProc(void* inRefCon)
void BGM_TaskQueue::WorkerThreadProc(semaphore_t inWorkQueuedSemaphore, semaphore_t inSyncTaskCompletedSemaphore, TAtomicStack<BGM_Task>* inTasks, TAtomicStack2<BGM_Task>* __nullable inFreeList, std::function<bool(BGM_Task*)> inProcessTask)
{
bool threadShouldStop = false;
bool theThreadShouldStop = false;
while(!threadShouldStop)
while(!theThreadShouldStop)
{
// Wait until a thread signals that it's added tasks to the queue.
//
@@ -317,18 +334,33 @@ void BGM_TaskQueue::WorkerThreadProc(semaphore_t inWorkQueuedSemaphore, semap
BGM_Task* theTask = inTasks->pop_all_reversed();
while(theTask != NULL &&
!threadShouldStop) // Stop processing tasks if we're shutting down
!theThreadShouldStop) // Stop processing tasks if we're shutting down
{
BGM_Task* theNextTask = theTask->mNext;
BGMAssert(!theTask->IsComplete(),
"BGM_TaskQueue::WorkerThreadProc: Cannot process already completed task (ID %d)",
theTask->GetTaskID());
BGMAssert(theTask != theNextTask,
"BGM_TaskQueue::WorkerThreadProc: BGM_Task %p (ID %d) was added to %s multiple times. arg1=%llu arg2=%llu",
theTask,
theTask->GetTaskID(),
(inTasks == &mRealTimeThreadTasks ? "mRealTimeThreadTasks" : "mNonRealTimeThreadTasks"),
theTask->GetArg1(),
theTask->GetArg2());
// Process the task
threadShouldStop = inProcessTask(theTask);
theTask->MarkCompleted();
BGM_Task* theNextTask = theTask->next();
theThreadShouldStop = inProcessTask(theTask);
// If the task was queued synchronously, let the thread that queued it know we're finished
if(theTask->IsSync())
{
// Marking the task as completed allows QueueSync to return, which means it's possible for theTask to point to
// invalid memory after this point.
CAMemoryBarrier();
theTask->MarkCompleted();
// Signal any threads waiting for their task to be processed.
//
// We use semaphore_signal_all instead of semaphore_signal to avoid a race condition in QueueSync. It's possible
@@ -336,7 +368,7 @@ void BGM_TaskQueue::WorkerThreadProc(semaphore_t inWorkQueuedSemaphore, semap
// added to the queue. So after each task is completed we have every waiting thread check if it was theirs.
//
// Note that semaphore_signal_all has an implicit barrier.
kern_return_t theError = semaphore_signal_all(inSyncTaskCompletedSemaphore);
theError = semaphore_signal_all(inSyncTaskCompletedSemaphore);
BGM_Utils::ThrowIfMachError("BGM_TaskQueue::WorkerThreadProc", "semaphore_signal_all", theError);
}
else if(inFreeList != NULL)
@@ -399,6 +431,13 @@ bool BGM_TaskQueue::ProcessNonRealTimeThreadTask(BGM_Task* inTask)
bool didStartIO = BGM_ClientTasks::StartIONonRT(theClients, static_cast<UInt32>(inTask->GetArg2()));
inTask->SetReturnValue(didStartIO);
}
// TODO: Catch the other types of exceptions BGM_ClientTasks::StartIONonRT can throw here as well. Set the task's return
// value (rather than rethrowing) so the exceptions can be handled if the task was queued sync. Then
// QueueSync_StartClientIO can throw some exception and BGM_StartIO can return an appropriate error code to the
// HAL, instead of the driver just crashing.
//
// Do the same for the kBGMTaskStopClientIO case below. And should we set a return value in the catch block for
// BGM_InvalidClientException as well, so it can also be rethrown in QueueSync_StartClientIO and then handled?
catch(BGM_InvalidClientException)
{
DebugMsg("BGM_TaskQueue::ProcessNonRealTimeThreadTask: Ignoring BGM_InvalidClientException thrown by StartIONonRT. %s",
+3 -3
View File
@@ -71,7 +71,7 @@ private:
class BGM_Task
{
public:
BGM_Task(BGM_TaskID inTaskID = kBGMTaskUninitialized, bool inIsSync = false, UInt64 inArg1 = 0, UInt64 inArg2 = 0) : mTaskID(inTaskID), mIsSync(inIsSync), mArg1(inArg1), mArg2(inArg2) { };
BGM_Task(BGM_TaskID inTaskID = kBGMTaskUninitialized, bool inIsSync = false, UInt64 inArg1 = 0, UInt64 inArg2 = 0) : mNext(NULL), mTaskID(inTaskID), mIsSync(inIsSync), mArg1(inArg1), mArg2(inArg2) { };
BGM_TaskID GetTaskID() { return mTaskID; }
UInt64 GetArg1() { return mArg1; }
@@ -84,7 +84,8 @@ private:
bool IsSync() { return mIsSync; }
// Used by TAtomicStack
BGM_Task* __nullable & next() { return mNext; };
BGM_Task* __nullable & next() { return mNext; }
BGM_Task* __nullable mNext;
private:
BGM_TaskID mTaskID;
@@ -93,7 +94,6 @@ private:
UInt64 mReturnValue = INT64_MAX;
bool mIsComplete = false;
bool mIsSync;
BGM_Task* __nullable mNext = NULL;
};
public:
+2 -2
View File
@@ -84,8 +84,8 @@ UInt64 WaitForBGMAppToStartOutputDevice()
theConnection.interruptionHandler = failureHandler;
theConnection.invalidationHandler = failureHandler;
// This remote call to BGMXPCHelper will send a reply when the output device is ready to receive IO. Note that we shouldn't trust
// the reply string.
// This remote call to BGMXPCHelper will send a reply when the output device is ready to receive IO. Note that, for security
// reasons, we shouldn't trust the reply object.
[[theConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
#if !DEBUG
#pragma unused (error)
@@ -172,6 +172,29 @@ std::vector<BGM_Client> BGM_ClientMap::GetClientsByPID(pid_t inPID) const
return theClients;
}
CACFArray BGM_ClientMap::CopyClientPIDsAndBundleIDs() const
{
// Since this is a read-only, non-real-time operation we can read from the shadow maps to avoid
// locking the main maps.
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
CACFArray theClientPIDsAndBundleIDs(false);
for(auto& theClientEntry : mClientMapShadow)
{
CACFDictionary theClientDict(false);
theClientDict.AddSInt32(CFSTR(kBGMClientsKey_ProcessID), theClientEntry.second.mProcessID);
// todo: is this a memory leak? (over retaining mBundleID, cause adding it to the dict also retains it) check for the same bug
// in CopyClientIntoAppVolumesArray if so.
theClientDict.AddString(CFSTR(kBGMClientsKey_BundleID), theClientEntry.second.mBundleID.CopyCFString());
theClientPIDsAndBundleIDs.AppendDictionary(theClientDict.GetDict());
}
return theClientPIDsAndBundleIDs;
}
#pragma mark Music Player
void BGM_ClientMap::UpdateMusicPlayerFlags(pid_t inMusicPlayerPID)
@@ -213,7 +236,7 @@ void BGM_ClientMap::UpdateMusicPlayerFlagsInShadowMaps(std::function<bool(BGM
CACFArray BGM_ClientMap::CopyClientRelativeVolumesAsAppVolumes(CAVolumeCurve inVolumeCurve) const
{
// Since this is a read-only, non-real-time operation, we can read from the shadow maps to avoid
// Since this is a read-only, non-real-time operation we can read from the shadow maps to avoid
// locking the main maps.
CAMutex::Locker theShadowMapsLocker(mShadowMapsMutex);
@@ -101,6 +101,10 @@ private:
public:
std::vector<BGM_Client> GetClientsByPID(pid_t inPID) const;
// todo: comment
// todo: would it be useful at all to have a similar property that returns past clients instead? probably not...
CACFArray CopyClientPIDsAndBundleIDs() const;
// Set the isMusicPlayer flag for each client. (True if the client has the given bundle ID/PID, false otherwise.)
void UpdateMusicPlayerFlags(pid_t inMusicPlayerPID);
void UpdateMusicPlayerFlags(CACFString inMusicPlayerBundleID);
@@ -302,7 +302,7 @@ bool BGM_Clients::SetClientsRelativeVolumes(const CACFArray inAppVolumes)
// Each element in appVolumes is a CFDictionary containing the process id and/or bundle id of an app, and its
// new relative volume
for(int i = 0; i < inAppVolumes.GetNumberItems(); i++)
for(UInt32 i = 0; i < inAppVolumes.GetNumberItems(); i++)
{
CACFDictionary theAppVolume(false);
inAppVolumes.GetCACFDictionary(i, theAppVolume);
@@ -108,6 +108,8 @@ public:
// Returns true if any clients' relative volumes were changed.
bool SetClientsRelativeVolumes(const CACFArray inAppVolumes);
CACFArray CopyClientPIDsAndBundleIDs() const { return mClientMap.CopyClientPIDsAndBundleIDs(); }
private:
BGM_ClientMap mClientMap;
+98 -5
View File
@@ -26,25 +26,118 @@
// Local Includes
#include "BGM_TestUtils.h"
// BGMDriver Includes
#include "BGM_Types.h"
// PublicUtility Includes
#include "CAException.h"
@interface BGM_DeviceTests : XCTestCase
@end
@implementation BGM_DeviceTests {
}
- (void)setUp {
@implementation BGM_DeviceTests
- (void) setUp {
[super setUp];
}
- (void)tearDown {
- (void) tearDown {
// Reminder: add code here, above the super call
[super tearDown];
}
- (void) testCustomPropertyMusicPlayerBundleID {
BGM_Device& device = BGM_Device::GetInstance();
// Convenience wrappers
auto getBundleID = [&](UInt32 inDataSize = sizeof(CFStringRef)){
CFStringRef bundleID = NULL;
UInt32 outDataSize;
device.GetPropertyData(/* inObjectID = */ kObjectID_Device,
/* inClientPID = */ 3,
/* inAddress = */ kBGMMusicPlayerBundleIDAddress,
/* inQualifierDataSize = */ 0,
/* inQualifierData = */ NULL,
/* inDataSize = */ inDataSize,
/* outDataSize = */ outDataSize,
/* outData = */ reinterpret_cast<void* __nonnull>(&bundleID));
// This isn't technically required, but we're unlikely to ever want to return any more/less data from GetPropertyData.
XCTAssertEqual(outDataSize, sizeof(CFStringRef));
return (__bridge_transfer NSString*)bundleID;
};
auto setBundleID = [&](const CFStringRef* __nullable bundleID, UInt32 dataSize = sizeof(CFStringRef)){
device.SetPropertyData(/* inObjectID = */ kObjectID_Device,
/* inClientPID = */ 1234,
/* inAddress = */ kBGMMusicPlayerBundleIDAddress,
/* inQualifierDataSize = */ 0,
/* inQualifierData = */ NULL,
/* inDataSize = */ dataSize,
/* inData = */ reinterpret_cast<const void* __nonnull>(bundleID));
};
// Should be set to the empty string by default.
XCTAssertEqualObjects(getBundleID(), @"");
// Should be able to set the property to an arbitrary string. (Purposefully not using CFSTR for this one just in case it
// makes a difference.)
CFStringRef newID = CFStringCreateWithCString(kCFAllocatorDefault, "test.bundle.ID", kCFStringEncodingUTF8);
setBundleID(&newID);
CFRelease(newID);
XCTAssertEqualObjects(getBundleID(), @"test.bundle.ID");
// Should be able to set the property back to the empty string.
newID = CFSTR("");
setBundleID(&newID);
XCTAssertEqualObjects(getBundleID(), @"");
// Arguments should be null-checked.
BGMShouldThrow<BGM_RuntimeException>(self, [&](){
UInt32 outDataSize;
device.GetPropertyData(kObjectID_Device, 0, kBGMMusicPlayerBundleIDAddress, 0, NULL, sizeof(CFStringRef),
outDataSize, /* outData = */ reinterpret_cast<void* __nonnull>(NULL));
});
BGMShouldThrow<BGM_RuntimeException>(self, [&](){
setBundleID(NULL);
});
// Invalid data should be rejected.
BGMShouldThrow<CAException>(self, [&](){
setBundleID((CFStringRef*)&kCFNull);
});
BGMShouldThrow<CAException>(self, [&](){
CFStringRef nullRef = NULL;
setBundleID(&nullRef);
});
BGMShouldThrow<CAException>(self, [&](){
CFArrayRef array = (__bridge_retained CFArrayRef)@[ @1, @2 ];
setBundleID((CFStringRef*)&array);
});
// Should throw if not given enough space for the return data.
BGMShouldThrow<CAException>(self, [&](){
getBundleID(/* inDataSize = */ 0);
});
newID = CFSTR("bundle");
// Passing more data than needed should be fine as long as it starts with a CFStringRef.
setBundleID(&newID, sizeof(CFStringRef) * 2);
// Should throw if not enough data is passed.
BGMShouldThrow<CAException>(self, [&](){
setBundleID(&newID, sizeof(CFStringRef) - 1);
});
}
// TODO: Performance tests?
- (void)testPerformanceExample {
- (void) testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
+13
View File
@@ -5,6 +5,19 @@
Firstly, thanks for reading this. Pull requests, bug reports, feature requests, etc. are all very welcome (including
ones from non-developers).
For bug reports about `build_and_install.sh`, please include your `build_and_install.log`. (It should be written to the
same directory as `build_and_install.sh`.)
For bug reports about Background Music itself, if you feel like being really helpful, you could reproduce your bug with
a debug build and include the relevant logs. But don't feel obligated to. You can build and install a debug build with
`./build_and_install.sh -d`.
BGMDriver and BGMXPCHelper log messages to system.log by default. You can read them in Console.app. BGMApp's logs go to
stdout, except errors or warnings that should be visible to users, which go to syslog. To get BGMApp's logs either run
it in Xcode or from a terminal (e.g. `$ /Applications/Background\ Music.app/Contents/MacOS/Background\ Music`).
I'm working on adding logging to release builds and generally making the process easier.
The code is mostly C++ and Objective-C. But don't worry if you don't know those languages--I don't either. Or Core
Audio, for that matter.
+46
View File
@@ -0,0 +1,46 @@
<!-- vim: set tw=120: -->
# Manual Build and Install
- Install the virtual audio device `Background Music Device.driver` to `/Library/Audio/Plug-Ins/HAL`.
```shell
sudo xcodebuild -project BGMDriver/BGMDriver.xcodeproj \
-target "Background Music Device" \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
install
```
- Install the XPC helper.
```shell
sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \
-target BGMXPCHelper \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
INSTALL_PATH="$(BGMApp/BGMXPCHelper/safe_install_dir.sh)" \
install
```
- Install `Background Music.app` to `/Applications` (or wherever).
```shell
xcodebuild -project BGMApp/BGMApp.xcodeproj \
-target "Background Music" \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
install
```
- Restart `coreaudiod`: <br>
(Audio will stop working until the next step, so you might want to pause any running audio apps.)
```shell
sudo launchctl kill -15 system/com.apple.audio.coreaudiod
```
or, if that fails
```shell
sudo killall coreaudiod
```
- Run `Background Music.app`.
+39 -15
View File
@@ -14,10 +14,10 @@
- No restart required to install
- Runs entirely in userspace
**Version 0.1.0**, first release. Probably very buggy. Pretty much only tested on one system. (A MacBook running OS X
10.11 using the built-in audio device.)
**Version 0.1.0**, first release. Probably very buggy. Not very polished. Pretty much only tested on one system. (A
MacBook running OS X 10.11 using the built-in audio device.)
**Requires OS X 10.10+**. Might work on 10.9, but I haven't tried it.
**Requires OS X 10.10+**. Might work on 10.9, but I haven't tried it. Currently unable to build in Xcode 6.
## Auto-pause music
@@ -25,10 +25,10 @@ Background Music can pause your music player app when other audio starts playing
that when I'm listening to music and pause it to watch a video or something I always forget to unpause it afterwards. So
this keeps me from wearing headphones for hours listening to nothing.
So far iTunes, [Spotify](https://www.spotify.com), [VLC](https://www.videolan.org/vlc/) and
[VOX](http://coppertino.com/vox/mac) are supported, but adding support for a music player should only take a few minutes
(see `BGMMusicPlayer.h`). If you don't know how to program, or just don't feel like it, create an issue and I'll try to
add it for you.
So far iTunes, [Spotify](https://www.spotify.com), [Decibel](https://sbooth.org/Decibel/),
[VLC](https://www.videolan.org/vlc/) and [VOX](https://coppertino.com/vox/mac) are supported, but adding support for a
music player should only take a few minutes (see `BGMMusicPlayer.h`). If you don't know how to program, or just don't
feel like it, create an issue and I'll try to add it for you.
## App volumes
@@ -47,12 +47,37 @@ Background Music Device. You can create the aggregate device using the Audio MID
## Install
No binaries yet, but building should take less than a minute. To build and install everything, clone/download the
project and run the `build_and_install.sh` script. Unfortunately, **it won't build if you don't have Xcode installed**
because xcodebuild doesn't work on its own anymore.
No binaries yet (working on it) but building should take less than a minute.
The script restarts the system audio process (coreaudiod) at the end of the installation, so you might want to pause any
apps playing audio.
<table>
<tr><td>⚠️</td>
<td>Unfortunately, <strong>it won't build if you don't have Xcode installed</strong> because xcodebuild doesn't work on its own anymore. If you don't mind a 4GB download, you can <a href="https://developer.apple.com/xcode/download/">get Xcode here</a>.</td>
</tr></table>
If you're comfortable with it, you can just paste the following at a Terminal prompt.
[//]: # (Uses /bin/bash instead of just bash on the off chance that someone has a non standard Bash in their $PATH, but)
[//]: # (it doesn't do that for Tar or cURL because I'm fairly sure any versions of them should work here. That said,)
[//]: # (build_and_install.sh doesn't call things by absolute paths (yet?) anyway.)
[//]: # ( )
[//]: # (Uses "gzcat - | tar x" instead of "tar xz" because gzcat will also check the file's integrity. (Gzip files)
[//]: # (include a checksum.))
```shell
(set -eo pipefail; URL='https://github.com/kyleneideck/BackgroundMusic/archive/master.tar.gz'; \
cd $(mktemp -d); echo Downloading $URL to $(pwd); curl -qfL# $URL | gzcat - | tar x && \
/bin/bash BackgroundMusic-master/build_and_install.sh && rm -rf BackgroundMusic-master)
```
Otherwise, to build and install everything, do the following:
- Clone or [download](https://github.com/kyleneideck/BackgroundMusic/archive/master.zip) the project
- If the project is in a zip, unzip it
- [Go to the folder of the project](https://github.com/0nn0/terminal-mac-cheatsheet/wiki/Terminal-Cheatsheet-for-Mac-(-basics-)#core-commands) on your terminal
- Run the following the command: `/bin/bash build_and_install.sh`.
The script restarts the system audio process (coreaudiod) at the end of the installation, so you might want to pause any apps playing audio.
Additional detailed installation instructions can be found on [the Wiki](https://github.com/kyleneideck/BackgroundMusic/wiki/Installation).
## Uninstall
@@ -83,7 +108,7 @@ change the default device and then change it back again. Failing that, you might
General tab of Skype's preferences.
- Plugging in or unplugging headphones when Background Music isn't running can silence system audio. To fix it, go to
the Sound section in System Preferences, click the Output tab and change your default output device to something other
than Background Music Device.
than Background Music Device. Alternatively, you may Option+Click on the Sound icon in the menu bar to select a different output device.
This happens when OS X remembers that Background Music Device was your default audio device the last time you last
used (or didn't use) headphones.
@@ -118,6 +143,7 @@ change the default device and then change it back again. Failing that, you might
### Non-free
- [Audio Hijack](https://rogueamoeba.com/audiohijack/) - "Capture Audio From Anywhere on Your Mac"
- [Volume Mixer For Mac](http://www.volumemixer-app.com/) - "Application specific volume control for Mac OS Yosemite and
El Capitan"
- [Sound Siphon](http://staticz.com) - System/app audio recording, per-app volumes, system audio equaliser
@@ -127,5 +153,3 @@ change the default device and then change it back again. Failing that, you might
## License
GPLv2 or later
+55 -34
View File
@@ -23,7 +23,7 @@
#ifndef __SharedSource__BGM_Types__
#define __SharedSource__BGM_Types__
#include <CoreAudio/AudioServerPlugin.h>
#include <CoreAudio/AudioServerPlugIn.h>
#pragma mark IDs
@@ -36,9 +36,9 @@
#define kBGMDeviceUID "BGMDevice"
#define kBGMDeviceModelUID "BGMDeviceModelUID"
// The object IDs for the audio objects this driver implements
// The object IDs for the audio objects this driver implements.
//
// Regardless of the wrapped device, this driver always publishes this fixed set of objects. We might need to
// BGMDevice always publishes this fixed set of objects (regardless of the wrapped device). We might need to
// change that at some point, but so far it hasn't caused any problems and it makes the driver much simpler.
enum
{
@@ -50,12 +50,15 @@ enum
kObjectID_Mute_Output_Master = 6
};
#pragma mark Custom properties
#pragma mark BGMDevice Custom Properties
enum
{
// TODO: Combine the two music player properties
// Remember to update the values BGM_Device returns for kAudioObjectPropertyCustomPropertyInfoList if you
// modify this enum.
// The process ID of the music player as a CFNumber. Setting this property will also clear the value of
// kAudioDeviceCustomPropertyMusicPlayerBundleID. We use 0 to mean unset.
//
@@ -74,13 +77,17 @@ enum
// A CFBoolean similar to kAudioDevicePropertyDeviceIsRunning except it ignores whether IO is running for
// BGMApp. This is so BGMApp knows when it can stop doing IO to save CPU.
kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp = 'runo',
// A CFArray of CFDictionaries that each contain an app's pid, bundle ID and volume relative to other
// A CFArray of CFDictionaries that each contain an app's PID, bundle ID and volume relative to other
// running apps. See the dictionary keys below for more info.
//
// Getting this property will only return apps with volumes other than the default. Setting this property
// will add new app volumes or replace existing ones, but there's currently no way to delete an app from
// the internal collection.
kAudioDeviceCustomPropertyAppVolumes = 'apvs'
kAudioDeviceCustomPropertyAppVolumes = 'apvs',
// todo: send the client ids as well. maybe combine with kAudioDeviceCustomPropertyAppVolumes
// A CFArray with one entry for each client of BGMDevice. Each entry is a CFDictionary that contains the
// client's PID and, if it has one, the client's bundle ID. See the dictionary keys below for more info.
kAudioDeviceCustomPropertyClients = 'clts'
};
// The number of silent/audible frames before BGMDriver will change kAudioDeviceCustomPropertyDeviceAudibleState
@@ -104,11 +111,18 @@ enum
// the midpoint increases the client's volume and a value less than the midpoint decreases it. A volume curve is
// applied to kBGMAppVolumesKey_RelativeVolume when it's first set and then each of the app's samples are multiplied
// by it.
#define kBGMAppVolumesKey_RelativeVolume "rvol"
// The app's pid as a CFNumber. May be omitted if kBGMAppVolumesKey_BundleID is present.
#define kBGMAppVolumesKey_ProcessID "pid"
#define kBGMAppVolumesKey_RelativeVolume "av_rvol"
// The app's process ID as a CFNumber (which wraps a pid_t). May be omitted if kBGMAppVolumesKey_BundleID is present.
#define kBGMAppVolumesKey_ProcessID "av_pid"
// The app's bundle ID as a CFString. May be omitted if kBGMAppVolumesKey_ProcessID is present.
#define kBGMAppVolumesKey_BundleID "bid"
#define kBGMAppVolumesKey_BundleID "av_bid"
// kAudioDeviceCustomPropertyClients keys
//
// The client's process ID as a CFNumber<pid_t>.
#define kBGMClientsKey_ProcessID "c_pid"
// The client's bundle ID as a CFString. Omitted if the client doesn't have a bundle ID.
#define kBGMClientsKey_BundleID "c_bid"
// Volume curve range for app volumes
#define kAppRelativeVolumeMaxRawValue 100
@@ -116,31 +130,9 @@ enum
#define kAppRelativeVolumeMinDbValue -96.0f
#define kAppRelativeVolumeMaxDbValue 0.0f
#pragma mark XPC Return Codes
#pragma mark BGMDevice Custom Property Addresses
enum {
kBGMXPC_Success,
kBGMXPC_MessageFailure,
kBGMXPC_Timeout,
kBGMXPC_BGMAppStateError,
kBGMXPC_HardwareError,
kBGMXPC_InternalError
};
#pragma mark Exceptions
#if defined(__cplusplus)
class BGM_InvalidClientException { };
class BGM_InvalidClientPIDException { };
class BGM_InvalidClientRelativeVolumeException { };
class BGM_DeviceNotSetException { };
#endif
#pragma mark Property Addresses
// For convenience
// For convenience.
static const AudioObjectPropertyAddress kBGMMusicPlayerProcessIDAddress = {
kAudioDeviceCustomPropertyMusicPlayerProcessID,
@@ -172,5 +164,34 @@ static const AudioObjectPropertyAddress kBGMAppVolumesAddress = {
kAudioObjectPropertyElementMaster
};
static const AudioObjectPropertyAddress kBGMClientsAddress = {
kAudioDeviceCustomPropertyClients,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
#pragma mark XPC Return Codes
enum {
kBGMXPC_Success,
kBGMXPC_MessageFailure,
kBGMXPC_Timeout,
kBGMXPC_BGMAppStateError,
kBGMXPC_HardwareError,
kBGMXPC_InternalError
};
#pragma mark Exceptions
#if defined(__cplusplus)
class BGM_InvalidClientException { };
class BGM_InvalidClientPIDException { };
class BGM_InvalidClientRelativeVolumeException { };
class BGM_DeviceNotSetException { };
class BGM_RuntimeException { };
#endif
#endif /* __SharedSource__BGM_Types__ */
+8
View File
@@ -30,6 +30,14 @@
// System Includes
#include <mach/mach_error.h>
// The Assert macro from CADebugMacros with support for format strings added.
#define BGMAssert(inCondition, inMessage, ...) \
if(!(inCondition)) \
{ \
DebugMsg(inMessage, ## __VA_ARGS__); \
__ASSERT_STOP; \
}
class BGM_Utils
{
+6 -4
View File
@@ -13,8 +13,6 @@ There are also lots of other TODOs commented around the code.
- Should we hide music players in the preferences menu if Launch Services says they aren't installed? (And they aren't
running or otherwise obviously present on the system.) If so, should we have a "show all" menu item?
- Disable the Auto-pause Music menu item if the selected music player isn't running
- System-wide volume boost
- Listen for notifications when the output device is removed/disabled and revert to the previous output device
@@ -87,9 +85,13 @@ There are also lots of other TODOs commented around the code.
- When BGMApp changes the default device to BGMDevice, some apps seem to keep using the previous default device if they
were running IO at the time. Not sure if we can do anything about this.
- Figure out how to test BGMDriver with Address Sanitizer enabled. It isn't working because coreaudiod will try to read
files outside of its sandbox.
- Figure out how to test BGMDriver with Address Sanitizer enabled. It isn't working because it makes coreaudiod try to
read files outside of its sandbox and the system kills it.
- Crash reporting
- Test Background Music on a system running OS X on a case-sensitive file system. In
[#64](https://github.com/kyleneideck/BackgroundMusic/issues/64), BGMDriver failed to compile and the error suggests it
was a case-sensitivity problem. That should be fixed now, but I haven't looked for any runtime bugs.
+357 -88
View File
@@ -22,29 +22,68 @@
# Copyright © 2016 Kyle Neideck
# Copyright © 2016 Nick Jacques
#
# Builds and installs BGMApp, BGMDriver and BGMXPCHelper. Requires xcodebuild.
# Builds and installs BGMApp, BGMDriver and BGMXPCHelper. Requires xcodebuild and Xcode.
#
# Safe mode
set -euo pipefail
IFS=$'\n\t'
# General error message
# Subshells and function inherit the ERR trap
set -o errtrace
general_error() {
echo "$(tput setaf 1)ERROR$(tput sgr0): Install script failed at line $1. This is probably a" \
"bug in the script. Feel free to report it." >&2
}
trap 'general_error ${LINENO}' ERR
# Build for release by default.
# TODO: Add an option to use the debug configuration?
# Go to the project directory.
cd "$( dirname "${BASH_SOURCE[0]}" )"
error_handler() {
local LAST_COMMAND="${BASH_COMMAND}" LAST_COMMAND_EXIT_STATUS=$?
# Log the error.
echo "Failure in ${0} at line ${1}. The last command was (probably)" >> ${LOG_FILE}
echo " ${LAST_COMMAND}" >> ${LOG_FILE}
echo "which exited with status ${LAST_COMMAND_EXIT_STATUS}." >> ${LOG_FILE}
echo "Error message: ${ERROR_MSG}" >> ${LOG_FILE}
echo >> ${LOG_FILE}
# Scrub username from log (and also real name just in case).
sed -i'tmp' "s/$(whoami)/[username removed]/g" ${LOG_FILE}
sed -i'tmp' "s/$(id -F)/[name removed]/g" ${LOG_FILE}
rm "${LOG_FILE}tmp"
# Print an error message.
echo "$(tput setaf 9)ERROR$(tput sgr0): Install failed at line $1 with the message:"
echo
echo -e "${ERROR_MSG}" >&2
echo >&2
echo "Feel free to report this. If you do, you'll probably want to include the" \
"build_and_install.log file from this directory ($(pwd)). But quickly skim through it" \
"first to check that it doesn't include any personal information. It shouldn't, but this" \
"is alpha software so you never know." >&2
echo >&2
echo "To try building and installing without this build script, see MANUAL-INSTALL.md." >&2
# Finish logging debug info if the script fails early.
if ! [[ -z ${LOG_DEBUG_INFO_TASK_PID:-} ]]; then
wait ${LOG_DEBUG_INFO_TASK_PID}
fi
}
# Build for release by default. Use -d for a debug build.
CONFIGURATION=Release
#CONFIGURATION=Debug
# The default is to clean before installing because we want the log file to have roughly the same
# information after every build.
CLEAN=clean
CONTINUE_ON_ERROR=0
# Update .gitignore if you change this.
LOG_FILE=build_and_install.log
# Empty the log file
echo -n > ${LOG_FILE}
COREAUDIOD_PLIST="/System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist"
# TODO: Should (can?) we use xcodebuild to get these from the Xcode project rather than duplicating
@@ -56,6 +95,41 @@ DRIVER_DIR="Background Music Device.driver"
XPC_HELPER_PATH="$(BGMApp/BGMXPCHelper/safe_install_dir.sh)"
XPC_HELPER_DIR="BGMXPCHelper.xpc"
GENERAL_ERROR_MSG="Internal script error. Probably a bug in this script."
BUILD_FAILED_ERROR_MSG="A build command failed. Probably a compilation error."
BGMAPP_FAILED_TO_START_ERROR_MSG="Background Music (${APP_PATH}/${APP_DIR}) didn't seem to start \
up. It might just be taking a while.
If it didn't install correctly, you'll need to open the Sound control panel in System Preferences \
and change your output device at least once. Your sound probably won't work until you do. (Or you \
restart your computer.)
If you only have one device, you can create a temporary one by opening \
\"/Applications/Utilities/Audio MIDI Setup.app\", clicking the plus button and choosing \"Create \
Multi-Output Device\"."
ERROR_MSG="${GENERAL_ERROR_MSG}"
XCODEBUILD="/usr/bin/xcodebuild"
if ! [[ -x "${XCODEBUILD}" ]]; then
XCODEBUILD=$(which xcodebuild || true)
fi
# This check is last because it takes 10 seconds or so if it fails.
if ! [[ -x "${XCODEBUILD}" ]]; then
XCODEBUILD=$(/usr/bin/xcrun --find xcodebuild &2>>${LOG_FILE} || true)
fi
# TODO: Update this when/if Xcode 6 is supported.
RECOMMENDED_MIN_XCODE_VERSION=7
usage() {
echo "Usage: $0 [options]" >&2
echo -e "\t-n\tDon't clean before building/installing." >&2
echo -e "\t-d\tDebug build. (Release is the default.)" >&2
echo -e "\t-c\tContinue on script errors. Might not be safe." >&2
echo -e "\t-h\tPrint this usage statement." >&2
exit 1
}
bold_face() {
echo $(tput bold)$*$(tput sgr0)
}
@@ -66,10 +140,15 @@ is_alive() {
}
# Shows a "..." animation until the previous command finishes. Shows an error message and exits the
# script if the command fails.
# script if the command fails. The return value will be the exit status of the command.
#
# Takes an optional timeout in seconds. The return value will be the exit status of the command.
# Params:
# - The error message to show if the previous command fails.
# - An optional timeout in seconds.
show_spinner() {
set +e
trap - ERR
local PREV_COMMAND_PID=$!
# Get the previous command as a string, with variables resolved. Assumes that if the command has
@@ -77,76 +156,233 @@ show_spinner() {
# one child.)
local CHILD_PID=$(pgrep -P ${PREV_COMMAND_PID} | head -n1 || echo ${PREV_COMMAND_PID})
local PREV_COMMAND_STRING=$(ps -o command= ${CHILD_PID})
local TIMEOUT=${1:-0}
local TIMEOUT=${2:-0}
(I=1;
while (is_alive ${PREV_COMMAND_PID}) && ([[ ${TIMEOUT} -lt 1 ]] || [[ $I -lt ${TIMEOUT} ]]); do
printf '.';
sleep 1;
# Erase after we've printed three dots. (\b is backspace.)
[[ $(($I % 3)) -eq 0 ]] && printf '\b\b\b \b\b\b';
I=$(($I + 1));
done) &
exec 3>&1 # Creates an alias so the following subshell can print to stdout.
DID_TIMEOUT=$(
I=1
while (is_alive ${PREV_COMMAND_PID}) && \
([[ ${TIMEOUT} -lt 1 ]] || [[ $I -lt ${TIMEOUT} ]])
do
printf '.' >&3
sleep 1
# Erase after we've printed three dots. (\b is backspace.)
[[ $((I % 3)) -eq 0 ]] && printf '\b\b\b \b\b\b' >&3
((I++))
done
if [[ $I -eq ${TIMEOUT} ]]; then
kill ${PREV_COMMAND_PID} >> ${LOG_FILE} 2>&1
echo 1
else
echo 0
fi)
exec 3<&- # Close the file descriptor.
set +e
wait ${PREV_COMMAND_PID}
local EXIT_STATUS=$?
set -e
# Clean up the dots.
printf '\b\b\b'
printf '\b\b\b \b\b\b'
# Print an error message if the command fails.
# (wait returns 127 if the process has already exited.)
if [[ ${EXIT_STATUS} -ne 0 ]] && [[ ${EXIT_STATUS} -ne 127 ]]; then
echo "$(tput setaf 1)ERROR$(tput sgr0): Build step failed. See ${LOG_FILE} for details." >&2
echo "Failed command:" >&2
echo " ${PREV_COMMAND_STRING}" >&2
ERROR_MSG="$1"
if [[ ${DID_TIMEOUT} -eq 0 ]]; then
ERROR_MSG+="\n\nFailed command:
${PREV_COMMAND_STRING}"
fi
exit ${EXIT_STATUS}
error_handler ${LINENO}
if [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then
exit ${EXIT_STATUS}
fi
fi
if [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then
set -e
trap 'error_handler ${LINENO}' ERR
fi
return ${EXIT_STATUS}
}
# Check for xcodebuild.
if [[ "$(which xcodebuild)" == "" ]]; then
echo "$(tput setaf 1)ERROR$(tput sgr0): Can't find xcodebuild in your \$PATH." >&2
echo >&2
echo "If you have Xcode installed, you should be able to install the command line developer" \
"tools, including xcodebuild, with" >&2
echo " xcode-select --install" >&2
echo "If not, you'll need to install Xcode (~9GB), because xcodebuild no longer works without" \
"it." >&2
parse_options() {
while getopts ":ndch" opt; do
case $opt in
n)
CLEAN=""
;;
d)
CONFIGURATION="Debug"
;;
c)
CONTINUE_ON_ERROR=1
echo "$(tput setaf 11)WARNING$(tput sgr0): Ignoring errors."
set +e
trap - ERR
;;
h)
usage
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
;;
esac
done
}
# Disable error handlers.
trap - ERR
set +e
check_xcode() {
RETURN=0
# Check for Xcode.
XCODE_PATH=$(which xcode-select > /dev/null && xcode-select --print-path)
XCODE_PATH=${XCODE_PATH%/Contents/Developer}
if [[ "${XCODE_PATH}" == "" ]] && [[ -d /Applications/Xcode.app ]]; then
XCODE_PATH="/Applications/Xcode.app"
# First, check xcodebuild exists on the system an is an executable.
if ! [[ -x "${XCODEBUILD}" ]] || ! xcode-select --print-path &>/dev/null || \
! pkgutil --pkg-info=com.apple.pkg.CLTools_Executables &>/dev/null; then
RETURN=1
fi
if [[ "${XCODE_PATH}" == "" ]] && [[ -d ~/Applications/Xcode.app ]]; then
XCODE_PATH="~/Applications/Xcode.app"
# Check that Xcode is installed, not just the command line tools.
if ! "${XCODEBUILD}" -version &>/dev/null; then
((RETURN+=2))
fi
if [[ "${XCODE_PATH}" != "" ]]; then
# Exit with an error message if we couldn't find a working xcodebuild.
if [[ ${RETURN} -eq 0 ]] && \
# Version check.
[[ "$(echo ${XCODE_VERSION} | sed 's/\..*$//g')" -lt ${RECOMMENDED_MIN_XCODE_VERSION} ]]
then
RETURN=-1
fi
exit ${RETURN}
}
# Expects CHECK_XCODE_TASK_PID to be set.
handle_check_xcode_result() {
if [[ -z ${HANDLED_CHECK_XCODE_RESULT:-} ]]; then
HANDLED_CHECK_XCODE_RESULT=1
# Wait for the Xcode checks to finish.
set +e
trap - ERR
wait ${CHECK_XCODE_TASK_PID}
CHECK_XCODE_TASK_STATUS=$?
trap 'error_handler ${LINENO}' ERR
set -e
# If there was a problem with Xcode/xcodebuild, print the error message and exit.
if [[ ${CHECK_XCODE_TASK_STATUS} -ne 0 ]]; then
handle_check_xcode_failure ${CHECK_XCODE_TASK_STATUS}
fi
fi
}
handle_check_xcode_failure() {
# No command line tools
if [[ $1 -eq 1 ]] || [[ $1 -eq 3 ]]; then
echo "$(tput setaf 9)ERROR$(tput sgr0): The Xcode Command Line Tools don't seem to be" \
"installed on your system." >&2
echo >&2
echo "If you have Xcode installed, you should be able to install them with" >&2
echo " xcode-select --install" >&2
echo "If not, you'll need to install Xcode (~9GB), because xcodebuild no longer works" \
"without it." >&2
echo >&2
echo "It looks like you have Xcode installed to ${XCODE_PATH}" >&2
fi
exit 1
# No Xcode
if [[ $1 -eq 2 ]] || [[ $1 -eq 3 ]]; then
echo "$(tput setaf 9)ERROR$(tput sgr0): Unfortunately, Xcode (~9GB) is required to build" \
"Background Music, but ${XCODEBUILD} doesn't appear to be usable. You may need to" \
"tell the Xcode command line tools where your Xcode is installed to with" >&2
echo " xcode-select --switch /The/path/to/your/Xcode.app" >&2
echo >&2
echo "Output from ${XCODEBUILD}:" >&2
"${XCODEBUILD}" -version >&2 || true
echo >&2
fi
if [[ $1 -eq -1 ]]; then
# Xcode version is probably too old.
echo "$(tput setaf 11)WARNING$(tput sgr0): Your version of Xcode (${XCODE_VERSION}) may" \
"not be recent enough to build Background Music." >&2
else
# Disable error handlers
set +e; trap - ERR
# Look for an Xcode install.
echo "Looking for Xcode..." >&2
XCODE_PATHS=$(mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode' || \
kMDItemCFBundleIdentifier == 'com.apple.Xcode'")
if [[ "${XCODE_PATHS}" != "" ]]; then
echo "It looks like you have Xcode installed to" >&2
echo "${XCODE_PATHS}" >&2
else
echo "Not found." >&2
fi
if [[ ${CONTINUE_ON_ERROR} -eq 0 ]]; then
exit 1
fi
fi
}
log_debug_info() {
# Log some environment details, version numbers, etc. This takes a while, so we do it in the
# background.
(set +e; trap - ERR
echo "Background Music Build Log" >> ${LOG_FILE}
echo "----" >> ${LOG_FILE}
echo "System details:" >> ${LOG_FILE}
sw_vers >> ${LOG_FILE} 2>&1
# The same as uname -a, except without printing the nodename (for privacy).
uname -mrsv >> ${LOG_FILE} 2>&1
/bin/bash --version >> ${LOG_FILE} 2>&1
/usr/bin/env python --version >> ${LOG_FILE} 2>&1
echo "On git branch: $(git rev-parse --abbrev-ref HEAD 2>&1)" >> ${LOG_FILE}
echo "Most recent commit: $(git rev-parse HEAD 2>&1)" \
"(\"$(git show -s --format=%s HEAD 2>&1)\")" >> ${LOG_FILE}
echo "Using xcodebuild: ${XCODEBUILD}" >> ${LOG_FILE}
echo "Using BGMXPCHelper path: ${XPC_HELPER_PATH}" >> ${LOG_FILE}
xcode-select --version >> ${LOG_FILE} 2>&1
echo "Xcode path: $(xcode-select --print-path 2>&1)" >> ${LOG_FILE}
echo "Xcode version:" >> ${LOG_FILE}
xcodebuild -version >> ${LOG_FILE} 2>&1
echo "Xcode SDKs:" >> ${LOG_FILE}
xcodebuild -showsdks >> ${LOG_FILE} 2>&1
xcrun --version >> ${LOG_FILE} 2>&1
echo "Clang version:" >> ${LOG_FILE}
$(/usr/bin/xcrun --find clang 2>&1) --version >> ${LOG_FILE} 2>&1
echo "launchctl version: $(launchctl version 2>&1)" >> ${LOG_FILE}
echo "----" >> ${LOG_FILE}) &
LOG_DEBUG_INFO_TASK_PID=$!
}
# Register our handler so we can print a message and clean up if there's an error.
trap 'error_handler ${LINENO}' ERR
parse_options "$@"
# Warn if running as root.
if [[ $(id -u) -eq 0 ]]; then
echo "$(tput setaf 11)WARNING$(tput sgr0): This script is not intended to be run as root. Run" \
"it normally and it'll sudo when it needs to." >&2
fi
# Go to the project directory.
cd "$( dirname "${BASH_SOURCE[0]}" )"
# Print initial message.
echo "$(bold_face About to install Background Music). Please pause all audio, if you can."
[[ "${CONFIGURATION}" == "Debug" ]] && echo "Debug build."
echo
echo "This script will install:"
echo " - ${APP_PATH}/${APP_DIR}"
@@ -154,6 +390,12 @@ echo " - ${DRIVER_PATH}/${DRIVER_DIR}"
echo " - ${XPC_HELPER_PATH}/${XPC_HELPER_DIR}"
echo " - /Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
echo
# Make sure Xcode and the command line tools are installed and recent enough.
XCODE_VERSION=$(${XCODEBUILD} -version | head -n 1 | awk '{ print $2 }' 2>/dev/null || echo 0)
check_xcode &
CHECK_XCODE_TASK_PID=$!
read -p "Continue (y/N)? " CONTINUE_INSTALLATION
if [[ "${CONTINUE_INSTALLATION}" != "y" ]] && [[ "${CONTINUE_INSTALLATION}" != "Y" ]]; then
@@ -161,66 +403,87 @@ if [[ "${CONTINUE_INSTALLATION}" != "y" ]] && [[ "${CONTINUE_INSTALLATION}" != "
exit 0
fi
# If the check_xcode process has already finished, we can check the result early.
if ! is_alive ${CHECK_XCODE_TASK_PID}; then
handle_check_xcode_result
fi
# Update the user's sudo timestamp. (Prompts the user for their password.)
if ! sudo -v; then
# Don't call sudo -v if this is a Travis CI build.
if ([[ -z ${TRAVIS:-} ]] || [[ "${TRAVIS}" != true ]]) && ! sudo -v; then
echo "ERROR: This script must be run by a user with administrator (sudo) privileges." >&2
exit 1
fi
handle_check_xcode_result
log_debug_info
# BGMDriver
echo "[1/3] Installing the virtual audio device $(bold_face ${DRIVER_DIR}) to" \
"$(bold_face ${DRIVER_PATH})." \
| tee ${LOG_FILE}
| tee -a ${LOG_FILE}
# Disable the -e shell option here so we can handle the error differently.
(set +e;
sudo xcodebuild -project BGMDriver/BGMDriver.xcodeproj \
-target "Background Music Device" \
-configuration ${CONFIGURATION} \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
install >> ${LOG_FILE} 2>&1) &
# Disable the -e shell option and error trap for build commands so we can handle errors differently.
(set +e; trap - ERR
sudo "${XCODEBUILD}" -project BGMDriver/BGMDriver.xcodeproj \
-target "Background Music Device" \
-configuration ${CONFIGURATION} \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
${CLEAN} install >> ${LOG_FILE} 2>&1) &
show_spinner
show_spinner "${BUILD_FAILED_ERROR_MSG}"
# BGMXPCHelper
echo "[2/3] Installing $(bold_face ${XPC_HELPER_DIR}) to $(bold_face ${XPC_HELPER_PATH})." \
| tee -a ${LOG_FILE}
(set +e;
sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \
-target BGMXPCHelper \
-configuration ${CONFIGURATION} \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
INSTALL_PATH="${XPC_HELPER_PATH}" \
install >> ${LOG_FILE} 2>&1) &
(set +e; trap - ERR
sudo "${XCODEBUILD}" -project BGMApp/BGMApp.xcodeproj \
-target BGMXPCHelper \
-configuration ${CONFIGURATION} \
OTHER_CFLAGS="-Wno-everything" \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
INSTALL_PATH="${XPC_HELPER_PATH}" \
${CLEAN} install >> ${LOG_FILE} 2>&1) &
show_spinner
show_spinner "${BUILD_FAILED_ERROR_MSG}"
# BGMApp
echo "[3/3] Installing $(bold_face ${APP_DIR}) to $(bold_face ${APP_PATH})." \
| tee -a ${LOG_FILE}
(set +e;
sudo xcodebuild -project BGMApp/BGMApp.xcodeproj \
-target "Background Music" \
-configuration ${CONFIGURATION} \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
install >> ${LOG_FILE} 2>&1) &
(set +e; trap - ERR
sudo "${XCODEBUILD}" -project BGMApp/BGMApp.xcodeproj \
-target "Background Music" \
-configuration ${CONFIGURATION} \
OTHER_CFLAGS="-Wno-everything" \
RUN_CLANG_STATIC_ANALYZER=0 \
DSTROOT="/" \
${CLEAN} install >> ${LOG_FILE} 2>&1) &
show_spinner
show_spinner "${BUILD_FAILED_ERROR_MSG}"
# Fix Background Music.app owner/group.
# (We have to run xcodebuild as root to install BGMXPCHelper because it installs to directories
#
# We have to run xcodebuild as root to install BGMXPCHelper because it installs to directories
# owned by root. But that means the build directory gets created by root, and since BGMApp uses the
# same build directory we have to run xcodebuild as root to install BGMApp as well.)
# same build directory we have to run xcodebuild as root to install BGMApp as well.
#
# TODO: Can't we just chown -R the build dir before we install BGMApp? Then we wouldn't have to
# install BGMApp as root. (But maybe still handle the unlikely case of APP_PATH not being
# user-writable.)
sudo chown -R "$(whoami):admin" "${APP_PATH}/${APP_DIR}"
# Fix the build directories' owner/group. This is mainly so the whole source directory can be
# deleted easily after installing.
sudo chown -R "$(whoami):admin" "BGMApp/build" "BGMDriver/build"
# Restart coreaudiod.
echo "Restarting coreaudiod to load the virtual audio device." \
@@ -236,7 +499,7 @@ echo "Restarting coreaudiod to load the virtual audio device." \
(sudo launchctl unload "${COREAUDIOD_PLIST}" &>/dev/null && \
sudo launchctl load "${COREAUDIOD_PLIST}" &>/dev/null) || \
sudo killall coreaudiod &>/dev/null) && \
sleep 2
sleep 5
# Invalidate sudo ticket
sudo -k
@@ -246,13 +509,19 @@ sudo -k
# device after restarting coreaudiod and this is the easiest way.
echo "Launching Background Music."
ERROR_MSG="${BGMAPP_FAILED_TO_START_ERROR_MSG}"
open "${APP_PATH}/${APP_DIR}"
# Ignore script errors from this point.
set +e
trap - ERR
# Wait up to 5 seconds for Background Music to start.
(while ! (ps -Ao ucomm= | grep 'Background Music' > /dev/null); do
sleep 1;
done) &
show_spinner 5
(trap 'exit 1' TERM
while ! (ps -Ao ucomm= | grep 'Background Music' > /dev/null); do
sleep 1
done) &
show_spinner "${BGMAPP_FAILED_TO_START_ERROR_MSG}" 5
echo "Done."
+49
View File
@@ -0,0 +1,49 @@
// TODO: Delete this before merging WIP-MultiprocessAppVols into master.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <libproc.h>
#include <errno.h>
extern int responsibility_get_responsible_for_pid(pid_t, int32_t*, uint64_t*, size_t*, char*);
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s <pid>\n", argv[0]);
return -1;
}
pid_t pid = (pid_t)atoi(argv[1]);
{
char path_buf[PROC_PIDPATHINFO_MAXSIZE] = "";
size_t path_len = sizeof(path_buf);
if (proc_pidpath(pid, path_buf, path_len) <= 0) {
printf("Couldn't get pid path for pid %d\n", pid);
printf("Error %d: %s\n", errno, strerror(errno));
return -1;
}
path_buf[path_len - 1] = '\0';
printf("Path for process: %s\n", path_buf);
}
{
int32_t rpid;
uint64_t urpid;
char responsible_path_buf[PROC_PIDPATHINFO_MAXSIZE] = "";
size_t responsible_path_len = sizeof(responsible_path_buf);
if (responsibility_get_responsible_for_pid(pid, &rpid, &urpid, &responsible_path_len, responsible_path_buf) != 0) {
printf("Couldn't get responsibility pid for pid %d\n", pid);
printf("Error %d: %s\n", errno, strerror(errno));
return -1;
}
responsible_path_buf[responsible_path_len - 1] = '\0';
printf("Path for responsible process: %s\n", responsible_path_buf);
printf("Responsible PID: %d\n", rpid);
printf("Responsible unique PID: %llu\n", urpid);
}
return 0;
}
+3 -2
View File
@@ -75,8 +75,8 @@ read -p "Continue (y/N)? " user_prompt
if [ "$user_prompt" == "y" ] || [ "$user_prompt" == "Y" ]; then
# Ensure that the user can use sudo
if ! sudo -v; then
# Ensure that the user can use sudo. (But not if this is a Travis CI build, because then it would fail.)
if ([[ -z ${TRAVIS:-} ]] || [[ "${TRAVIS}" != true ]]) && ! sudo -v; then
echo "ERROR: This script must be run by a user with administrator (sudo) privileges." >&2
exit 1
fi
@@ -148,6 +148,7 @@ if [ "$user_prompt" == "y" ] || [ "$user_prompt" == "Y" ]; then
# Invalidate sudo ticket
sudo -k
# TODO: What if they only have one audio device?
echo -e "\n${bold}Done! Toggle your sound output device in the Sound control panel to complete the uninstall.${normal}"
osascript -e 'tell application "System Preferences"