Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d4e1bff10 | |||
| 1c743053c6 | |||
| de1a9fa413 | |||
| 12a7deb15f | |||
| e6375b0919 | |||
| 029cb2f5f8 | |||
| 2ac8a1afd1 | |||
| 784bb249a9 | |||
| 51e1266532 | |||
| 4b7caeeb54 | |||
| 7e592ebb2b | |||
| 0aea54f0c5 | |||
| 9407ee36d2 | |||
| 9058e3c556 | |||
| a8f4ace099 | |||
| 610f15c556 | |||
| 62174be95e | |||
| 48bdcd1a64 | |||
| cb6f38821e | |||
| b96fcb7a47 | |||
| ad14e1b0a4 | |||
| 61be8bead4 | |||
| 8b2bf56eca | |||
| e655d571e0 | |||
| 9257d50b15 | |||
| 3097f4b621 | |||
| 73f221e423 | |||
| 7d700b3de5 | |||
| 014a6eecc8 | |||
| 2cc3faaebb | |||
| 5000c64084 | |||
| 4fc776e4a6 | |||
| ca3c497940 | |||
| 08837fcdca | |||
| 076514c83a | |||
| 8090f12204 | |||
| bd38207146 | |||
| d92d9e6d29 | |||
| d048287140 | |||
| c89566d212 | |||
| cbb3e20e1c | |||
| 9f9c18748a | |||
| 3040a20766 | |||
| 30ec1c24b7 | |||
| cc2516a705 | |||
| 909615617a | |||
| 89eebb0e02 | |||
| 058af733be | |||
| ffd634245c | |||
| 7afb8a6c12 | |||
| c68e05244c | |||
| 22dea1ef5d | |||
| b2770f1793 | |||
| a730fb98f8 | |||
| 1fdf47cd12 | |||
| 4608f4bbac | |||
| 5f1641da8f | |||
| 2e1fb9db46 | |||
| fbf95c0605 | |||
| ada2615d88 | |||
| 71c11315e5 | |||
| 4e9d21d499 | |||
| da96132da0 | |||
| d84471c29e | |||
| 6ef13ed762 | |||
| 8ddfb81148 | |||
| f4ea06e3c3 | |||
| 01217d2d8c | |||
| 5a048431b2 | |||
| 333aaf93e2 | |||
| 9441a31d9d | |||
| f31aebcf51 | |||
| 3f4b119c86 | |||
| 3bfd68615f | |||
| 24b248e09d | |||
| 0d5965b266 | |||
| c84dd5754c | |||
| a5b7cd69e3 | |||
| fe786fb338 | |||
| 5a5f9d0a8d | |||
| 89186d526f | |||
| 69fb7f69e3 | |||
| b38f6ddcd6 | |||
| e9e13a3056 | |||
| c7df7506bc | |||
| 169ee92802 | |||
| 0a87596c1c | |||
| 3551e0f6e6 | |||
| 6d23685084 | |||
| a93ec5ee34 | |||
| 26d354866e | |||
| 06cf3d8777 | |||
| 0e0cc5fd05 | |||
| 213c339309 | |||
| c0a556be04 | |||
| a62b783e5f | |||
| 44e3e96986 | |||
| d9a22d1b62 | |||
| ce56a96829 | |||
| c10ea9d132 | |||
| 64003ca1d6 | |||
| 7df7f062e0 | |||
| 2090761ae5 | |||
| c303f60343 | |||
| 97d3645bd3 | |||
| 9765193c1f | |||
| 2ff4c08e75 | |||
| 3a529cc0fb | |||
| 0f653f90a8 | |||
| 4c40e0286e | |||
| bdebc07b38 | |||
| 65c2a34ce5 | |||
| 94459f7e8c | |||
| ef4bd54965 | |||
| c0ab98b98b | |||
| 043bfb3633 | |||
| 63393054ba | |||
| 6486c8ccf3 | |||
| c068810953 | |||
| dd05f60c1d | |||
| df02aae263 | |||
| f20dd578ab | |||
| 026558d974 | |||
| ae724fe7af | |||
| 42fbd7590e | |||
| ae564d761b | |||
| 6edf25bfbe | |||
| 694c8d612e | |||
| f24a6cb2f1 | |||
| 70a64de734 | |||
| 488ed16dfc | |||
| 8499b85736 | |||
| 1a2c880b32 | |||
| 4cfc8df48a | |||
| 4ff3db4238 | |||
| 23ecf7e46e | |||
| 63b458abdc | |||
| d95b0d8b1a | |||
| 8de821d4aa | |||
| 08d428c948 | |||
| 1710b93333 | |||
| 2c1677305d | |||
| 8ca17bb5f8 | |||
| 120606b95e | |||
| 5fc615bdb5 | |||
| 40f0128dcd | |||
| 76e63965db | |||
| 151c41599e | |||
| bab1ec9e96 | |||
| 0913671557 | |||
| 3221888a96 | |||
| c9f2270b31 | |||
| c024116cd4 | |||
| 35d1b17c39 | |||
| e8e11bae32 | |||
| be2e9b46db | |||
| 2e8caeed48 | |||
| 8f04d9afc1 | |||
| ad13915d70 | |||
| 2b71621c33 | |||
| 884dd8fd0a | |||
| 904cb9b345 | |||
| 85d2df909e | |||
| 966987702c | |||
| 5c0d806e57 | |||
| fc55ce1c54 | |||
| d7cb6e9318 | |||
| e5043e3fe0 | |||
| c652955a04 | |||
| f3ef314cd9 | |||
| f61f998c51 | |||
| dfad77dc35 | |||
| 4c7eba30af | |||
| e6e784b013 | |||
| 8ed95eb83b | |||
| 7f6c0e9d4f | |||
| 9e50e2348b | |||
| 0388b26aa1 | |||
| 002afc0cd1 | |||
| a45335b65d | |||
| 0a7be7d32c | |||
| d26e9ee3d1 | |||
| 94fc1259e3 | |||
| a97529e812 | |||
| 0231e131df | |||
| 9951a82879 | |||
| 3ac7221cb1 | |||
| 2be4bab54f | |||
| 04f17301a1 | |||
| e616718eab | |||
| 503d1a92ec | |||
| 2939dbe28c | |||
| a40dfde439 | |||
| df9815a4be | |||
| 26dd2ee1ab | |||
| d89d1e7813 |
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Example bug report template
|
||||
|
||||
> Don't worry if you have trouble getting some of this info. Just leave it out.
|
||||
|
||||
**Description of the bug**
|
||||
> Please don't just say it's "not working".
|
||||
|
||||
**Steps to reproduce**
|
||||
> Steps to reproduce the bug. This usually doesn't need to be super detailed.
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. See error message '...'
|
||||
|
||||
**Versions**
|
||||
> Please complete the following information.
|
||||
- Background Music: [e.g. "0.4.3" or "0.4.0-SNAPSHOT-c0ab98b". `Preferences > About Background Music`]
|
||||
- macOS: [e.g. "11.3 Beta (20E5172i)" or "Big Sur". ` > About This Mac`]
|
||||
|
||||
**Hardware**
|
||||
> Delete this part if you think it's probably not necessary.
|
||||
- Computer: [e.g. "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)". ` > About This Mac`]
|
||||
- Audio Device: [e.g. "Built-in Output. Manufacturer: Apple Inc. Output Channels: 2 [...]". `System Information app > Hardware > Audio`]
|
||||
|
||||
**Debug logs**
|
||||
> If you think the developers might not be able to reproduce the bug on their computers, e.g. because an important feature is completely broken and they would have noticed, it can help to include [debug logs](https://github.com/kyleneideck/BackgroundMusic/wiki/Getting-Debug-Logs). This takes a little effort, so feel free to leave it out at first.
|
||||
|
||||
[Debug logs attached here](https://github.com/example/background-music-debug-logs.txt)
|
||||
|
||||
**Other info**
|
||||
> Anything else you want to add?
|
||||
|
||||
---
|
||||
|
||||
> Tips
|
||||
> (Delete this section before posting.)
|
||||
> - https://github.com/kyleneideck/BackgroundMusic#troubleshooting
|
||||
> - Try the latest SNAPSHOT version from https://github.com/kyleneideck/BackgroundMusic/releases (if it's newer than the latest non-SNAPSHOT release).
|
||||
> - If your bug is one of these common issues, consider leaving a comment or a +1 (👍) on an existing issue:
|
||||
> - Background Music currently only supports audio devices with two channels. Bluetooth devices often only have one.
|
||||
> - Volumes having no effect for certain apps: Microsoft Teams ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/268#issuecomment-604977210)), Zoom ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/396#issuecomment-741992157)), Discord ([workaround](https://github.com/kyleneideck/BackgroundMusic/issues/210#issuecomment-507048957), [see also](https://github.com/kyleneideck/BackgroundMusic/issues/267#issuecomment-617327850)), Chrome (sometimes)
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Other
|
||||
about: Feature request, question, support request or anything else
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
> There's no template for this issue type. I just wanted to make it clear that it's OK to submit other types of issues.
|
||||
@@ -0,0 +1,216 @@
|
||||
# TODO: Split this into multiple .yml files? Multiple jobs?
|
||||
name: Build, Test and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
# Build and test in the same job because the UI tests expect BGMDriver to be installed.
|
||||
build-and-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
# TODO: Add older macOS versions.
|
||||
os:
|
||||
- macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Work in a case-sensitive disk image.
|
||||
# This lets us catch failures that only happen on case-sensitive filesystems.
|
||||
run: |
|
||||
hdiutil create \
|
||||
-type SPARSEBUNDLE \
|
||||
-fs 'Case-sensitive Journaled HFS+' \
|
||||
-volname bgmbuild \
|
||||
-nospotlight \
|
||||
-verbose \
|
||||
-attach \
|
||||
-size 100m \
|
||||
bgmbuild.dmg
|
||||
sudo cp -r . /Volumes/bgmbuild
|
||||
cd /Volumes/bgmbuild
|
||||
- name: Install coreutils for actions/runner/issues/884 workaround.
|
||||
# See https://github.com/actions/runner/issues/884#issuecomment-1018851327
|
||||
run: brew install coreutils
|
||||
- name: Build and install Background Music.
|
||||
run: |
|
||||
# `sudo` and `tput` expect this to be set.
|
||||
export TERM=xterm-256color
|
||||
genv --default-signal=PIPE yes | sudo ./build_and_install.sh
|
||||
- name: Print the log file.
|
||||
if: always()
|
||||
run: cat build_and_install.log
|
||||
- name: Log some checksums.
|
||||
run: 'find */build/Release/*/ -type f -exec md5 {} \;'
|
||||
- name: Log the installed audio devices and their IDs.
|
||||
run: |
|
||||
system_profiler SPAudioDataType
|
||||
say -a '?'
|
||||
- name: Check the BGM dirs and files were installed.
|
||||
run: |
|
||||
# These commands fail if the dir/file isn't found.
|
||||
ls -la "/Applications/Background Music.app"
|
||||
ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
|
||||
ls -la "/usr/local/libexec/BGMXPCHelper.xpc" \
|
||||
|| ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
|
||||
ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
|
||||
- name: Close BGMApp (which the install script opened).
|
||||
run: >-
|
||||
osascript -e 'tell application "Background Music" to quit'
|
||||
|| killall "Background Music"
|
||||
- name: Skip the UI tests. (They don't work on GitHub Actions yet.)
|
||||
run: BGMApp/BGMAppTests/UITests/skip-ui-tests.py
|
||||
- name: Run the tests.
|
||||
run: |
|
||||
echo '::group::BGMDriver Tests'
|
||||
xcodebuild \
|
||||
-quiet \
|
||||
-workspace BGM.xcworkspace \
|
||||
-scheme 'Background Music Device' \
|
||||
test
|
||||
echo '::endgroup::'
|
||||
|
||||
echo '::group::BGMXPCHelper Tests'
|
||||
xcodebuild \
|
||||
-quiet \
|
||||
-workspace BGM.xcworkspace \
|
||||
-scheme 'BGMXPCHelper' \
|
||||
test
|
||||
echo '::endgroup::'
|
||||
|
||||
# Grant BGMApp authorization to use input devices.
|
||||
# This is necessary for the UI tests because accepting the "Background Music would like to
|
||||
# use the microphone" dialog programmatically isn't reliable.
|
||||
# TODO: Commented out because we would need to generate the csreq (codesign signature)
|
||||
# value to match the BGMApp bundle the tests will run against.
|
||||
# dbPath="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
|
||||
# values="'kTCCServiceMicrophone','com.bearisdriving.BGM.App',0,2,2,1,X'FADE0C000000004800000001000000070000000800000014545ABE68FAF437700B14984BB24117EDDA1BBF2C0000000800000014386FB63B9CD6BA6E83CEDEAF4EDEE177C1FAEA92',NULL,NULL,'UNUSED',NULL,0,1652845317"
|
||||
# sqlQuery="INSERT OR IGNORE INTO access VALUES($values);"
|
||||
# sqlite3 "$dbPath" "$sqlQuery" || (echo "Failed to modify $dbPath"; exit 1)
|
||||
# # Log the added TCC.db entry.
|
||||
# sqlite3 "$dbPath" "select * from access where client like '%BGM%';"
|
||||
|
||||
echo '::group::BGMApp Tests'
|
||||
# TODO: Commented out in case it uses too much CPU.
|
||||
# log stream --info \
|
||||
# --predicate 'process == "coreaudiod" or
|
||||
# process == "Background Music" or
|
||||
# process == "BGMXPCHelper" or
|
||||
# composedMessage contains[cd] "Background Music" or
|
||||
# composedMessage contains "BGM"' > app.log &
|
||||
xcodebuild \
|
||||
-quiet \
|
||||
-workspace BGM.xcworkspace \
|
||||
-scheme 'Background Music' \
|
||||
test
|
||||
echo '::endgroup::'
|
||||
- name: Upload the test results.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bgm-test-results
|
||||
path: |
|
||||
/Users/runner/Library/Developer/Xcode/DerivedData/*/Logs/Test/*.xcresult
|
||||
app.log
|
||||
/Users/runner/Library/Logs/CrashReporter/*
|
||||
/Users/runner/Library/Logs/DiagnosticReports/*
|
||||
- name: Uninstall Background Music.
|
||||
run: |
|
||||
# `tput` expects this to be set.
|
||||
export TERM=xterm-256color
|
||||
genv --default-signal=PIPE yes | sudo ./uninstall.sh
|
||||
- name: Check the BGM dirs and files were removed.
|
||||
run: |
|
||||
if ls -la "/Applications/Background Music.app"; then exit 1; fi
|
||||
if ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"; then exit 1; fi
|
||||
if ls -la "/usr/local/libexec/BGMXPCHelper.xpc"; then exit 1; fi
|
||||
if ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"; then
|
||||
exit 1
|
||||
fi
|
||||
if ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"; then exit 1; fi
|
||||
release:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Build the .pkg installer.
|
||||
run: |
|
||||
# `sudo` and `tput` expect this to be set.
|
||||
export TERM=xterm-256color
|
||||
# If this build is for a tag with "DEBUG" in its name, build a debug package. (More
|
||||
# detailed logging, no optimization, etc.)
|
||||
if [[ "$GITHUB_REF" =~ .*DEBUG.* ]]; then
|
||||
sudo ./package.sh -d
|
||||
else
|
||||
sudo ./package.sh
|
||||
fi
|
||||
- name: Install the .pkg.
|
||||
# Delete archives/ first because it contains a copy of Background Music.app.
|
||||
# Background Music.app is "relocatable", which means that if the user moves it and then
|
||||
# installs a new version, macOS will put the new version in the same place. This makes sure
|
||||
# the installer puts Background Music.app in /Applications so the build won't fail when we
|
||||
# check that later.
|
||||
#
|
||||
# package.sh puts the archives in a zipfile next to the .pkg, so we can still upload them
|
||||
# after deleting the directory here.
|
||||
#
|
||||
# TODO: On TravisCI, this was failing for debug builds. We couldn't figure out why, so we
|
||||
# might have to ignore that with
|
||||
# || [[ "$GITHUB_REF" =~ .*DEBUG.* ]]
|
||||
run: |
|
||||
sudo rm -rf archives
|
||||
sudo installer \
|
||||
-pkg Background-Music-*/BackgroundMusic-*.pkg \
|
||||
-target / \
|
||||
-verbose \
|
||||
-dumplog
|
||||
- name: Print the installer logs.
|
||||
if: always()
|
||||
# This trims the start of the log to save space.
|
||||
run: grep -E -A 9999 -B 20 'Background.?Music' /var/log/install.log
|
||||
- name: Check the BGM dirs and files were installed.
|
||||
if: always()
|
||||
run: |
|
||||
ls -la "/Applications/Background Music.app"
|
||||
ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
|
||||
ls -la "/usr/local/libexec/BGMXPCHelper.xpc" \
|
||||
|| ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
|
||||
ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
|
||||
- name: Upload the .pkg installer and archives.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: pkg-installer
|
||||
path: Background-Music-*
|
||||
- name: Upload the log file from the package.sh build.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-and-install-log-for-pkg
|
||||
path: build_and_install.log
|
||||
# TODO: Create a GitHub release. This is the Travis YAML that was handling it:
|
||||
# deploy:
|
||||
# provider: releases
|
||||
# api_key:
|
||||
# secure: j5Gd[...]
|
||||
# file_glob: true
|
||||
# file: Background-Music-*/*
|
||||
# skip_cleanup: true
|
||||
# name: $TRAVIS_TAG
|
||||
# prerelease: true
|
||||
# draft: true
|
||||
# on:
|
||||
# repo: kyleneideck/BackgroundMusic
|
||||
# tags: true
|
||||
# # TODO: Use "condition" to build master and tags?
|
||||
# condition: $DEPLOY = true
|
||||
@@ -9,6 +9,7 @@ cmake-build-debug/
|
||||
BGM.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
|
||||
Images/*.aux
|
||||
Images/*.log
|
||||
/archives/
|
||||
|
||||
# Everything below is from https://github.com/github/gitignore/blob/master/Objective-C.gitignore
|
||||
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
language: objective-c
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode10
|
||||
xcode_sdk: macosx10.14
|
||||
sudo: required
|
||||
env: DEPLOY=true
|
||||
- os: osx
|
||||
osx_image: xcode9.4
|
||||
xcode_sdk: macosx10.13
|
||||
sudo: required
|
||||
- os: osx
|
||||
osx_image: xcode9.3
|
||||
xcode_sdk: macosx10.13
|
||||
sudo: required
|
||||
- os: osx
|
||||
osx_image: xcode9.2
|
||||
xcode_sdk: macosx10.13
|
||||
sudo: required
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
xcode_sdk: macosx10.12
|
||||
sudo: required
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
xcode_sdk: macosx10.11
|
||||
sudo: required
|
||||
env: PACKAGE=false
|
||||
# branches:
|
||||
# only:
|
||||
# - master
|
||||
install:
|
||||
# Install Apple's NullAudio device. Travis' VMs don't have any audio devices installed.
|
||||
- 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:
|
||||
# Build in a case-sensitive disk image to catch failures that only happen on case-sensitive filesystems.
|
||||
- hdiutil create -type SPARSEBUNDLE -fs 'Case-sensitive Journaled HFS+' -volname bgmbuild -nospotlight -verbose -attach -size 100m bgmbuild.dmg
|
||||
- sudo cp -r . /Volumes/bgmbuild
|
||||
- cd /Volumes/bgmbuild
|
||||
# Install Background Music.
|
||||
- yes | ./build_and_install.sh
|
||||
# Print the log file, but put it in a fold because it's so long.
|
||||
- echo -en 'build_and_install.log\ntravis_fold:start:build.log\\r'
|
||||
- cat build_and_install.log
|
||||
- echo -en 'travis_fold:end:build.log\\r'
|
||||
- find */build/Release/*/ -type f -exec md5 {} \;
|
||||
# Log the installed audio devices...
|
||||
- system_profiler SPAudioDataType
|
||||
# ...and their IDs.
|
||||
- say -a '?'
|
||||
# Check the BGM dirs and files were installed. (These fail if the dir/file isn't found.)
|
||||
- ls -la "/Applications/Background Music.app"
|
||||
- ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
|
||||
- ls -la "/usr/local/libexec/BGMXPCHelper.xpc" || ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
|
||||
- ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
|
||||
# Close BGMApp (which the install script opened).
|
||||
#
|
||||
# The killall fallback command is necessary because the AppleScript gets "user canceled" on Travis'
|
||||
# Xcode 9 images for some reason.
|
||||
- osascript -e 'tell application "Background Music" to quit' || killall "Background Music"
|
||||
# Skip the UI tests until Travis has support for them.
|
||||
- BGMApp/BGMAppTests/UITests/travis-skip.py
|
||||
# Run the tests.
|
||||
# The echo commands put the output into a fold in the Travis logs.
|
||||
- echo -en 'Unit Tests\ntravis_fold:start:tests\\r'
|
||||
- xcodebuild -workspace BGM.xcworkspace -scheme 'Background Music Device' test
|
||||
- xcodebuild -workspace BGM.xcworkspace -scheme 'Background Music' test
|
||||
- xcodebuild -workspace BGM.xcworkspace -scheme 'BGMXPCHelper' test
|
||||
- echo -en 'travis_fold:end:tests\\r'
|
||||
# Uninstall Background Music.
|
||||
- yes | ./uninstall.sh
|
||||
# Check the BGM dirs and files were removed.
|
||||
- if ls -la "/Applications/Background Music.app"; then false; fi
|
||||
- if ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"; then false; fi
|
||||
- if ls -la "/usr/local/libexec/BGMXPCHelper.xpc"; then false; fi
|
||||
- if ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"; then false; fi
|
||||
- if ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"; then false; fi
|
||||
# Return early if we're not testing packaging on this OS X version.
|
||||
- if [[ "$PACKAGE" == "false" ]]; then exit 0; fi
|
||||
# Build the .pkg installer. Print the build logs if it fails. If this build is for a tag with
|
||||
# "DEBUG" in its name, build a debug package. (More detailed logging, no optimization, etc.)
|
||||
- if [[ "$TRAVIS_TAG" =~ .*DEBUG.* ]]; then
|
||||
./package.sh -d || (cat build_and_install.log && travis_terminate 1);
|
||||
else
|
||||
./package.sh || (cat build_and_install.log && travis_terminate 1);
|
||||
fi
|
||||
# Install the .pkg.
|
||||
- sudo installer -pkg Background-Music-*/BackgroundMusic-*.pkg -target / -verbose -dumplog
|
||||
# Check the BGM dirs and files were installed again.
|
||||
- ls -la "/Applications/Background Music.app"
|
||||
- ls -la "/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
|
||||
- ls -la "/usr/local/libexec/BGMXPCHelper.xpc" || ls -la "/Library/Application Support/Background Music/BGMXPCHelper.xpc"
|
||||
- ls -la "/Library/LaunchDaemons/com.bearisdriving.BGM.XPCHelper.plist"
|
||||
# Post on IRC when Travis builds finish.
|
||||
notifications:
|
||||
irc: "irc.freenode.org#backgroundmusic"
|
||||
# Upload the .pkg and dSYM zip to GitHub.
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: j5GdMTkJI/9lfGMcAW4dnBnfNSW0EUGSuaKSXw49FfjfcshLL2RFxIbQkyA7QqjoJm6ohstU3tOCo7c9FrqIWjE/+5itGJpq7NXDRxFtd2qzcli1u+1IRvQUZJ4VYC9982pSS0IUynK9/f0rhbdkWsCuXWIjoClYPBRscc8soDBJvkDbfilPFfFgkc8TuSmtGDCdu9coGVi6b9HuTLNQU0g5DZkjmv71Vj3SwJ2CmvOk3GFfV1SjvG2SRgBDwyP1g9MRGRiNYkmK9lJRgsq2KLluzb04lt22x8RIcZ+kZYOQVmgDlCeWlOcXi0iz1wU/QzdoYFEAnJdG4q0hqKeqIi+p8Tc31nHPuc1ZlYpifzMQ6KuOoOP19eceJwriAT133t2RSB3Rl3nxh9bymNPNyQ2dJwGNFtO68f3aZsuE5L92lVgW/ipZ6e5Sw1ovXldR04mxNtyY4WvFXFlkn/776tKV0vgAubsHfceGM/aRoBj+E2gDvqkFqIR8wrZAZEeSM2reMHPMx5ICFppIZ8dCIVjF5bsxZQsbojY+LXV8BUU5kLAou0yD7Q+lHi9r3HYdN90+cC02HKGFYzsIiMAyf4IAngnLhwmmrLOwr3wWdACjYTJhznAZGNJh4lCeB4dx85iyj3EexJ6J/DL1k2+ZNKyMN3+i/215t+AvSsXuw5U=
|
||||
file_glob: true
|
||||
file: Background-Music-*/*
|
||||
skip_cleanup: true
|
||||
name: $TRAVIS_TAG
|
||||
prerelease: true
|
||||
on:
|
||||
repo: kyleneideck/BackgroundMusic
|
||||
tags: true
|
||||
# TODO: Use "condition" to build master and tags?
|
||||
condition: $DEPLOY = true
|
||||
|
||||
|
||||
@@ -8,11 +8,23 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
|
||||
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
|
||||
19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMPlayThroughRTLogger.cpp"; }; };
|
||||
19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };
|
||||
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMVolumeChangeListener.cpp"; }; };
|
||||
19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMMusic.m"; }; };
|
||||
19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */; };
|
||||
19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
|
||||
19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
|
||||
19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };
|
||||
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
|
||||
19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
|
||||
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
|
||||
19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73822ADD50BA9120AB05 /* BGMMusic.m */; };
|
||||
19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMDebugLogging.c"; }; };
|
||||
19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */; };
|
||||
19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */ = {isa = PBXBuildFile; fileRef = 19FE73389459BF65748F531F /* BGMDebugLogging.c */; };
|
||||
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */; };
|
||||
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; };
|
||||
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMStatusBarItem.mm"; }; };
|
||||
1C0BD0A51BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAutoPauseMusicPrefs.mm"; }; };
|
||||
1C0BD0A81BF1B029004F4CF5 /* BGMPreferencesMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C0BD0A71BF1B029004F4CF5 /* BGMPreferencesMenu.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMPreferencesMenu.mm"; }; };
|
||||
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C1465B71BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAutoPauseMusic.mm"; }; };
|
||||
@@ -53,8 +65,22 @@
|
||||
1C533C7B1EED2F6200270802 /* safe_install_dir.sh in Resources */ = {isa = PBXBuildFile; fileRef = 276972901CB16008007A2F7C /* safe_install_dir.sh */; };
|
||||
1C533C7C1EED2F8A00270802 /* com.bearisdriving.BGM.XPCHelper.plist.template in Resources */ = {isa = PBXBuildFile; fileRef = 2769728D1CAFCEFD007A2F7C /* com.bearisdriving.BGM.XPCHelper.plist.template */; };
|
||||
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */ = {isa = PBXBuildFile; fileRef = 1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */; };
|
||||
1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */; };
|
||||
1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */; };
|
||||
1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */; };
|
||||
1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */; };
|
||||
1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */; };
|
||||
1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */; };
|
||||
1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C62FE5423D423D700B9B68E /* XCTest.framework */; };
|
||||
1C62FE5823D4278300B9B68E /* skip-ui-tests.py in Resources */ = {isa = PBXBuildFile; fileRef = 1C62FE5623D4278300B9B68E /* skip-ui-tests.py */; };
|
||||
1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */; };
|
||||
1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMSystemSoundsVolume.mm"; }; };
|
||||
1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */; };
|
||||
1C8034D520B0347A004BC50C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8034D420B0347A004BC50C /* Security.framework */; };
|
||||
1C80DED320A6718600045BBE /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAppWatcher.m"; }; };
|
||||
1C80DED420A6718600045BBE /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; };
|
||||
1C8104AF22AD07E200B35517 /* BGMAppWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C80DED220A6718600045BBE /* BGMAppWatcher.m */; };
|
||||
1C8104B022AD082E00B35517 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; };
|
||||
1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMOutputVolumeMenuItem.mm"; }; };
|
||||
1C837DD91F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };
|
||||
1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */; };
|
||||
@@ -64,6 +90,13 @@
|
||||
1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMSwinsian.m"; }; };
|
||||
1C8D830520423E1C00A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; };
|
||||
1C8D830620423E2400A838F2 /* BGMSwinsian.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D8302204238DB00A838F2 /* BGMSwinsian.m */; };
|
||||
1C8D830B2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMGooglePlayMusicDesktopPlayer.m"; }; };
|
||||
1C8D830C2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */; };
|
||||
1C8D830E2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */ = {isa = PBXBuildFile; fileRef = 1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */; };
|
||||
1C8D830F2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */ = {isa = PBXBuildFile; fileRef = 1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */; };
|
||||
1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMGooglePlayMusicDesktopPlayerConnection.m"; }; };
|
||||
1C9258482090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; };
|
||||
1C9258492090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */; };
|
||||
1CACCF391F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMBackgroundMusicDevice.cpp"; }; };
|
||||
1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };
|
||||
1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */; };
|
||||
@@ -78,7 +111,6 @@
|
||||
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CC6593A1F91DEB400B0CCDC /* BGMTermination.mm */; };
|
||||
1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */; };
|
||||
1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */; };
|
||||
1CCC4F4E1E581C40008053E4 /* Mock_CAHALAudioObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */; };
|
||||
1CCC4F621E584100008053E4 /* BGMAppUITests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CCC4F611E584100008053E4 /* BGMAppUITests.mm */; };
|
||||
1CD1FD301BDDEAF2004F7E1B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */; };
|
||||
1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAppVolumesController.mm"; }; };
|
||||
@@ -123,9 +155,10 @@
|
||||
1CD989581ECFFD250014BBBF /* CAMutex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1963041BCAF468008A4DF7 /* CAMutex.cpp */; };
|
||||
1CD989591ECFFD250014BBBF /* CAPThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C8034C21BDAFD5700668E00 /* CAPThread.cpp */; };
|
||||
1CD9895A1ECFFD250014BBBF /* CARingBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */; };
|
||||
1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMDebugLoggingMenuItem.m"; }; };
|
||||
1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };
|
||||
1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */; };
|
||||
1CE7064C1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMOutputDeviceMenuSection.mm"; }; };
|
||||
1CEACF4D1F34793700FEC143 /* CAHALAudioDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962EB1BCABFC5008A4DF7 /* CAHALAudioDevice.cpp */; };
|
||||
1CEACF4F1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */; };
|
||||
1CED61691C3081C2002CAFCF /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 1CED61681C3081C2002CAFCF /* LICENSE */; };
|
||||
1CED616C1C316E1A002CAFCF /* BGMAudioDeviceManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMAudioDeviceManager.mm"; }; };
|
||||
1CF2D58F1F944773008B6E35 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1963021BCAC160008A4DF7 /* CoreAudio.framework */; };
|
||||
@@ -192,6 +225,8 @@
|
||||
27FB8C2F1DE468320084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGM_Utils.cpp"; }; };
|
||||
27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */; };
|
||||
27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */; };
|
||||
9E129A412602AE620005851B /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; settings = {COMPILER_FLAGS = "-frandom-seed=BGMApp-BGMASApplication.m"; }; };
|
||||
9E542C7026057FBA0016C0B5 /* BGMASApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E129A402602AE620005851B /* BGMASApplication.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -212,10 +247,19 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
19FE70CF6C93F5007940CE91 /* BGMMusic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMMusic.h; path = "Music Players/BGMMusic.h"; sourceTree = "<group>"; };
|
||||
19FE7179EBFA116F3861E79D /* BGMVolumeChangeListener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMVolumeChangeListener.cpp; sourceTree = "<group>"; };
|
||||
19FE71BCD79E7246F7345C16 /* BGMThreadSafetyAnalysis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMThreadSafetyAnalysis.h; sourceTree = "<group>"; };
|
||||
19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMPlayThroughRTLogger.h; sourceTree = "<group>"; };
|
||||
19FE73389459BF65748F531F /* BGMDebugLogging.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = BGMDebugLogging.c; path = PublicUtility/BGMDebugLogging.c; sourceTree = "<group>"; };
|
||||
19FE73822ADD50BA9120AB05 /* BGMMusic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMMusic.m; path = "Music Players/BGMMusic.m"; sourceTree = "<group>"; };
|
||||
19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughTests.mm; path = UnitTests/BGMPlayThroughTests.mm; sourceTree = "<group>"; };
|
||||
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMStatusBarItem.mm; sourceTree = "<group>"; };
|
||||
19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMDebugLogging.h; path = PublicUtility/BGMDebugLogging.h; sourceTree = "<group>"; };
|
||||
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMStatusBarItem.h; sourceTree = "<group>"; };
|
||||
19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMPlayThroughRTLogger.cpp; sourceTree = "<group>"; };
|
||||
19FE7FDAEBC3F0DB8C99823B /* BGMVolumeChangeListener.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMVolumeChangeListener.h; sourceTree = "<group>"; };
|
||||
1C09150723F010FB001EB0E1 /* set-version.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "set-version.sh"; sourceTree = "<group>"; };
|
||||
1C0BD0A31BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMAutoPauseMusicPrefs.h; path = Preferences/BGMAutoPauseMusicPrefs.h; sourceTree = "<group>"; };
|
||||
1C0BD0A41BF1A8E6004F4CF5 /* BGMAutoPauseMusicPrefs.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAutoPauseMusicPrefs.mm; path = Preferences/BGMAutoPauseMusicPrefs.mm; sourceTree = "<group>"; };
|
||||
1C0BD0A61BF1B029004F4CF5 /* BGMPreferencesMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMPreferencesMenu.h; path = Preferences/BGMPreferencesMenu.h; sourceTree = "<group>"; };
|
||||
@@ -265,6 +309,7 @@
|
||||
1C3D36711ED90E8600F98E66 /* BGMDeviceControlsList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMDeviceControlsList.h; sourceTree = "<group>"; };
|
||||
1C3DB4881BE0885A00EC8160 /* BGMAppVolumes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BGMAppVolumes.m; sourceTree = "<group>"; };
|
||||
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumes.h; sourceTree = "<group>"; };
|
||||
1C43DABE22F582780004AF35 /* BGMApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BGMApp.entitlements; 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>"; };
|
||||
@@ -272,16 +317,37 @@
|
||||
1C4D1A1C217C7D6400A1ACD0 /* BGMPreferredOutputDevices.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMPreferredOutputDevices.mm; sourceTree = "<group>"; };
|
||||
1C533C791EED28B700270802 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = uninstall.sh; path = ../../uninstall.sh; sourceTree = "<group>"; };
|
||||
1C533C7F1EF532CA00270802 /* _uninstall-non-interactive.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "_uninstall-non-interactive.sh"; sourceTree = "<group>"; };
|
||||
1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObject.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioObject.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioObject.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioObjects.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioSystemObject.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioDevice.h; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.h; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioDevice.cpp; path = BGMAppTests/UnitTests/Mocks/Mock_CAHALAudioDevice.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = MockAudioDevice.cpp; path = BGMAppTests/UnitTests/Mocks/MockAudioDevice.cpp; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObject.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObject.h; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MockAudioObjects.h; path = BGMAppTests/UnitTests/Mocks/MockAudioObjects.h; sourceTree = SOURCE_ROOT; };
|
||||
1C62FE5423D423D700B9B68E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
|
||||
1C62FE5623D4278300B9B68E /* skip-ui-tests.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; name = "skip-ui-tests.py"; path = "UITests/skip-ui-tests.py"; sourceTree = "<group>"; };
|
||||
1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = "BGMApp-Debug.entitlements"; sourceTree = "<group>"; };
|
||||
1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMPlayThroughRTLoggerTests.mm; path = UnitTests/BGMPlayThroughRTLoggerTests.mm; sourceTree = "<group>"; };
|
||||
1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMSystemSoundsVolume.h; sourceTree = "<group>"; };
|
||||
1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMSystemSoundsVolume.mm; sourceTree = "<group>"; };
|
||||
1C8034C21BDAFD5700668E00 /* CAPThread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CAPThread.cpp; path = PublicUtility/CAPThread.cpp; sourceTree = "<group>"; };
|
||||
1C8034C31BDAFD5700668E00 /* CAPThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CAPThread.h; path = PublicUtility/CAPThread.h; sourceTree = "<group>"; };
|
||||
1C8034D420B0347A004BC50C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||
1C80DED120A6718600045BBE /* BGMAppWatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppWatcher.h; sourceTree = "<group>"; };
|
||||
1C80DED220A6718600045BBE /* BGMAppWatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BGMAppWatcher.m; sourceTree = "<group>"; };
|
||||
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputVolumeMenuItem.h; sourceTree = "<group>"; };
|
||||
1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputVolumeMenuItem.mm; sourceTree = "<group>"; };
|
||||
1C8B0C69216205BF008C5679 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
1C8D8301204238DB00A838F2 /* Swinsian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Swinsian.h; path = "Music Players/Swinsian.h"; sourceTree = "<group>"; };
|
||||
1C8D8302204238DB00A838F2 /* BGMSwinsian.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMSwinsian.m; path = "Music Players/BGMSwinsian.m"; sourceTree = "<group>"; };
|
||||
1C8D8303204238DB00A838F2 /* BGMSwinsian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMSwinsian.h; path = "Music Players/BGMSwinsian.h"; sourceTree = "<group>"; };
|
||||
1C8D83092042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BGMGooglePlayMusicDesktopPlayer.h; path = "Music Players/BGMGooglePlayMusicDesktopPlayer.h"; sourceTree = "<group>"; };
|
||||
1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMGooglePlayMusicDesktopPlayer.m; path = "Music Players/BGMGooglePlayMusicDesktopPlayer.m"; sourceTree = "<group>"; };
|
||||
1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = GooglePlayMusicDesktopPlayer.js; path = "Music Players/GooglePlayMusicDesktopPlayer.js"; sourceTree = "<group>"; };
|
||||
1C9258452090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMGooglePlayMusicDesktopPlayerConnection.h; path = "Music Players/BGMGooglePlayMusicDesktopPlayerConnection.h"; sourceTree = "<group>"; };
|
||||
1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMGooglePlayMusicDesktopPlayerConnection.m; path = "Music Players/BGMGooglePlayMusicDesktopPlayerConnection.m"; sourceTree = "<group>"; };
|
||||
1CACCF371F3175AD007F86CA /* BGMBackgroundMusicDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BGMBackgroundMusicDevice.cpp; sourceTree = "<group>"; };
|
||||
1CACCF381F3175AD007F86CA /* BGMBackgroundMusicDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMBackgroundMusicDevice.h; sourceTree = "<group>"; };
|
||||
1CB8B3361BBA75EF000E2DD1 /* Background Music.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Background Music.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -303,16 +369,17 @@
|
||||
1CCC4F3C1E58196C008053E4 /* BGMXPCHelperTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BGMXPCHelperTests.m; path = BGMXPCHelperTests/BGMXPCHelperTests.m; sourceTree = SOURCE_ROOT; };
|
||||
1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "BGMAppUnitTests-Info.plist"; path = "UnitTests/BGMAppUnitTests-Info.plist"; sourceTree = "<group>"; };
|
||||
1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMMusicPlayersUnitTests.mm; path = UnitTests/BGMMusicPlayersUnitTests.mm; sourceTree = "<group>"; };
|
||||
1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioObject.cpp; path = UnitTests/Mock_CAHALAudioObject.cpp; sourceTree = "<group>"; };
|
||||
1CCC4F541E584081008053E4 /* BGMAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BGMAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "BGMAppUITests-Info.plist"; path = "UITests/BGMAppUITests-Info.plist"; sourceTree = "<group>"; };
|
||||
1CCC4F611E584100008053E4 /* BGMAppUITests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BGMAppUITests.mm; path = BGMAppTests/UITests/BGMAppUITests.mm; sourceTree = SOURCE_ROOT; };
|
||||
1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
|
||||
1CD410D21F9EDDAD0070A094 /* BGMAppVolumesController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMAppVolumesController.h; sourceTree = "<group>"; };
|
||||
1CD410D31F9EDDAD0070A094 /* BGMAppVolumesController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAppVolumesController.mm; sourceTree = "<group>"; };
|
||||
1CDE224022CBB95B0008E3AC /* Music.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Music.h; path = "Music Players/Music.h"; sourceTree = "<group>"; };
|
||||
1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BGMDebugLoggingMenuItem.h; sourceTree = "<group>"; };
|
||||
1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BGMDebugLoggingMenuItem.m; sourceTree = "<group>"; };
|
||||
1CE7064A1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMOutputDeviceMenuSection.h; sourceTree = "<group>"; };
|
||||
1CE7064B1BF1EC0600BFC06D /* BGMOutputDeviceMenuSection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMOutputDeviceMenuSection.mm; sourceTree = "<group>"; };
|
||||
1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mock_CAHALAudioSystemObject.cpp; path = UnitTests/Mock_CAHALAudioSystemObject.cpp; sourceTree = "<group>"; };
|
||||
1CED61681C3081C2002CAFCF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
1CED616A1C316E1A002CAFCF /* BGMAudioDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BGMAudioDeviceManager.h; sourceTree = "<group>"; };
|
||||
1CED616B1C316E1A002CAFCF /* BGMAudioDeviceManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BGMAudioDeviceManager.mm; sourceTree = "<group>"; };
|
||||
@@ -368,6 +435,8 @@
|
||||
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>"; };
|
||||
27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BGM_Utils.cpp; path = ../SharedSource/BGM_Utils.cpp; sourceTree = "<group>"; };
|
||||
9E129A3F2602AE620005851B /* BGMASApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BGMASApplication.h; path = Scripting/BGMASApplication.h; sourceTree = "<group>"; };
|
||||
9E129A402602AE620005851B /* BGMASApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BGMASApplication.m; path = Scripting/BGMASApplication.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -376,6 +445,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C8B0C6A216205BF008C5679 /* AVFoundation.framework in Frameworks */,
|
||||
1C8034D520B0347A004BC50C /* Security.framework in Frameworks */,
|
||||
270A84511E0044EF00F13C99 /* ScriptingBridge.framework in Frameworks */,
|
||||
1CD1FD301BDDEAF2004F7E1B /* AudioToolbox.framework in Frameworks */,
|
||||
1C1963031BCAC160008A4DF7 /* CoreAudio.framework in Frameworks */,
|
||||
@@ -386,6 +456,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C62FE5523D423D700B9B68E /* XCTest.framework in Frameworks */,
|
||||
1C8B0C6B21645355008C5679 /* AVFoundation.framework in Frameworks */,
|
||||
1CD989401ECFFCC50014BBBF /* AudioToolbox.framework in Frameworks */,
|
||||
);
|
||||
@@ -420,6 +491,15 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
1C09150623F010FB001EB0E1 /* Scripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1C09150723F010FB001EB0E1 /* set-version.sh */,
|
||||
);
|
||||
name = Scripts;
|
||||
path = ../SharedSource/Scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1C0BD0A21BF1A827004F4CF5 /* Preferences Menu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -471,6 +551,9 @@
|
||||
1C8034C31BDAFD5700668E00 /* CAPThread.h */,
|
||||
1C1962E21BC94E15008A4DF7 /* CARingBuffer.cpp */,
|
||||
1C1962E31BC94E15008A4DF7 /* CARingBuffer.h */,
|
||||
19FE7908A33FA7BD97B432D9 /* BGMDebugLogging.h */,
|
||||
19FE73389459BF65748F531F /* BGMDebugLogging.c */,
|
||||
19FE71BCD79E7246F7345C16 /* BGMThreadSafetyAnalysis.h */,
|
||||
);
|
||||
name = PublicUtility;
|
||||
sourceTree = "<group>";
|
||||
@@ -483,6 +566,8 @@
|
||||
1C2FC31D1EC723A100A76592 /* BGMASOutputDevice.h */,
|
||||
1C2FC31A1EC7238A00A76592 /* BGMASOutputDevice.mm */,
|
||||
1C2FC2FF1EB4D6E700A76592 /* BGMApp.sdef */,
|
||||
9E129A3F2602AE620005851B /* BGMASApplication.h */,
|
||||
9E129A402602AE620005851B /* BGMASApplication.m */,
|
||||
);
|
||||
name = Scripting;
|
||||
sourceTree = "<group>";
|
||||
@@ -491,6 +576,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
27D643B41C9FABBD00737F6E /* BGM_Types.h */,
|
||||
1C09150623F010FB001EB0E1 /* Scripts */,
|
||||
2771700F1CA0C83B00AB34B4 /* BGM_Utils.h */,
|
||||
27FB8C2E1DE468320084DB9D /* BGM_Utils.cpp */,
|
||||
27D643C41C9FBE5600737F6E /* BGM_TestUtils.h */,
|
||||
@@ -510,8 +596,14 @@
|
||||
1C2336D91BEAB6E7004C1C4E /* BGMMusicPlayer.m */,
|
||||
27F7D48E1D2483B100821C4B /* BGMDecibel.h */,
|
||||
27F7D48F1D2483B100821C4B /* BGMDecibel.m */,
|
||||
1C8D83092042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.h */,
|
||||
1C8D830A2042DE9500A838F2 /* BGMGooglePlayMusicDesktopPlayer.m */,
|
||||
1C9258452090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.h */,
|
||||
1C9258462090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m */,
|
||||
1C2336DB1BEAB73F004C1C4E /* BGMiTunes.h */,
|
||||
1C4699461BD5C0E400F78043 /* BGMiTunes.m */,
|
||||
19FE70CF6C93F5007940CE91 /* BGMMusic.h */,
|
||||
19FE73822ADD50BA9120AB05 /* BGMMusic.m */,
|
||||
1C2336DD1BEAE10C004C1C4E /* BGMSpotify.h */,
|
||||
1C2336DE1BEAE10C004C1C4E /* BGMSpotify.m */,
|
||||
279F48751DD6D73900768A85 /* BGMHermes.h */,
|
||||
@@ -527,6 +619,22 @@
|
||||
name = "Music Players";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1C62FE4423D3EAC500B9B68E /* Mocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1C62FE4823D3EB2D00B9B68E /* Mock_CAHALAudioSystemObject.cpp */,
|
||||
1C62FE4623D3EB2D00B9B68E /* Mock_CAHALAudioObject.cpp */,
|
||||
1C62FE4A23D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp */,
|
||||
1C62FE4D23D3EB2E00B9B68E /* MockAudioObjects.h */,
|
||||
1C62FE4723D3EB2D00B9B68E /* MockAudioObjects.cpp */,
|
||||
1C62FE4C23D3EB2E00B9B68E /* MockAudioObject.h */,
|
||||
1C62FE4523D3EB2D00B9B68E /* MockAudioObject.cpp */,
|
||||
1C62FE4923D3EB2E00B9B68E /* MockAudioDevice.h */,
|
||||
1C62FE4B23D3EB2E00B9B68E /* MockAudioDevice.cpp */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1CB8B32D1BBA75EF000E2DD1 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -559,8 +667,12 @@
|
||||
children = (
|
||||
1CB8B33B1BBA75EF000E2DD1 /* BGMAppDelegate.h */,
|
||||
1CB8B33C1BBA75EF000E2DD1 /* BGMAppDelegate.mm */,
|
||||
1C80DED120A6718600045BBE /* BGMAppWatcher.h */,
|
||||
1C80DED220A6718600045BBE /* BGMAppWatcher.m */,
|
||||
1C837DD61F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.h */,
|
||||
1C837DD71F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm */,
|
||||
1CE03A55239B56740036908D /* BGMDebugLoggingMenuItem.h */,
|
||||
1CE03A56239B56740036908D /* BGMDebugLoggingMenuItem.m */,
|
||||
1C780FF01FEF6C3B00497FAD /* BGMSystemSoundsVolume.h */,
|
||||
1C780FF11FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm */,
|
||||
1C3DB48A1BE0888500EC8160 /* BGMAppVolumes.h */,
|
||||
@@ -589,6 +701,8 @@
|
||||
1C3D36701ED90E8600F98E66 /* BGMDeviceControlsList.cpp */,
|
||||
1C1962E61BC94E91008A4DF7 /* BGMPlayThrough.h */,
|
||||
1C1962E51BC94E91008A4DF7 /* BGMPlayThrough.cpp */,
|
||||
19FE72A176FD500FB4C1F5C6 /* BGMPlayThroughRTLogger.h */,
|
||||
19FE7DE5E3BA0046ED2BC3C6 /* BGMPlayThroughRTLogger.cpp */,
|
||||
19FE799A86A285DD9423D164 /* BGMStatusBarItem.h */,
|
||||
19FE774DD758EC163EF4F28C /* BGMStatusBarItem.mm */,
|
||||
1CC6593B1F91DEB400B0CCDC /* BGMTermination.h */,
|
||||
@@ -609,6 +723,8 @@
|
||||
1CB8B3391BBA75EF000E2DD1 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1C43DABE22F582780004AF35 /* BGMApp.entitlements */,
|
||||
1C62FE5923D44FC000B9B68E /* BGMApp-Debug.entitlements */,
|
||||
275343BF1DFD01BC00DF3858 /* SystemPreferences.h */,
|
||||
1CED61681C3081C2002CAFCF /* LICENSE */,
|
||||
1CC1DF951BE8607700FB8FE4 /* Images.xcassets */,
|
||||
@@ -654,6 +770,7 @@
|
||||
children = (
|
||||
1CCC4F5F1E5840EF008053E4 /* BGMAppUITests-Info.plist */,
|
||||
1CCC4F491E581C0D008053E4 /* BGMAppUnitTests-Info.plist */,
|
||||
1C62FE5623D4278300B9B68E /* skip-ui-tests.py */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
@@ -662,8 +779,9 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1CCC4F4B1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm */,
|
||||
1CCC4F4C1E581C40008053E4 /* Mock_CAHALAudioObject.cpp */,
|
||||
1CEACF4E1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp */,
|
||||
19FE761D0371DEF9FDF053D6 /* BGMPlayThroughTests.mm */,
|
||||
1C687A6A23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm */,
|
||||
1C62FE4423D3EAC500B9B68E /* Mocks */,
|
||||
);
|
||||
name = "Unit Tests";
|
||||
sourceTree = "<group>";
|
||||
@@ -692,6 +810,8 @@
|
||||
27F7D4911D2484A300821C4B /* Decibel.h */,
|
||||
279F48781DD6D94000768A85 /* Hermes.h */,
|
||||
27379B851C7C54870084A24C /* iTunes.h */,
|
||||
1CDE224022CBB95B0008E3AC /* Music.h */,
|
||||
1C8D830D2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js */,
|
||||
27379B861C7C54870084A24C /* Spotify.h */,
|
||||
1C8D8301204238DB00A838F2 /* Swinsian.h */,
|
||||
27379B871C7C552A0084A24C /* VLC.h */,
|
||||
@@ -717,6 +837,8 @@
|
||||
2743CA1B1D86DA9B0089613B /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1C62FE5423D423D700B9B68E /* XCTest.framework */,
|
||||
1C8034D420B0347A004BC50C /* Security.framework */,
|
||||
1C8B0C69216205BF008C5679 /* AVFoundation.framework */,
|
||||
270A84501E0044EE00F13C99 /* ScriptingBridge.framework */,
|
||||
1CD1FD2F1BDDEAF2004F7E1B /* AudioToolbox.framework */,
|
||||
@@ -748,7 +870,7 @@
|
||||
1CB8B3321BBA75EF000E2DD1 /* Sources */,
|
||||
1CB8B3331BBA75EF000E2DD1 /* Frameworks */,
|
||||
1CB8B3341BBA75EF000E2DD1 /* Resources */,
|
||||
1CD440581E593DDD0064E0BC /* ShellScript */,
|
||||
1CD440581E593DDD0064E0BC /* Run Script - set-version.sh */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -784,7 +906,8 @@
|
||||
27379B8B1C7F57DA0084A24C /* Sources */,
|
||||
27379B8C1C7F57DA0084A24C /* Frameworks */,
|
||||
27379B8D1C7F57DA0084A24C /* Resources */,
|
||||
276972891CAFCE91007A2F7C /* ShellScript */,
|
||||
1C09150923F0208F001EB0E1 /* Run Script - set-version.sh */,
|
||||
276972891CAFCE91007A2F7C /* Run Script - post_install.sh */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -862,13 +985,13 @@
|
||||
};
|
||||
2743C9F51D86CFF90089613B = {
|
||||
CreatedOnToolsVersion = 8.0;
|
||||
ProvisioningStyle = Automatic;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 1CB8B3311BBA75EF000E2DD1 /* Build configuration list for PBXProject "BGMApp" */;
|
||||
compatibilityVersion = "Xcode 6.3";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -895,6 +1018,7 @@
|
||||
files = (
|
||||
274827951E11052500B31D8D /* MainMenu.xib in Resources */,
|
||||
1C533C7A1EED28B700270802 /* uninstall.sh in Resources */,
|
||||
1C8D830E2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */,
|
||||
1C533C801EF532CA00270802 /* _uninstall-non-interactive.sh in Resources */,
|
||||
1CED61691C3081C2002CAFCF /* LICENSE in Resources */,
|
||||
1C2FC3041EB4D6E700A76592 /* BGMApp.sdef in Resources */,
|
||||
@@ -906,6 +1030,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C62FE5823D4278300B9B68E /* skip-ui-tests.py in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -922,6 +1047,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C8D830F2042F25C00A838F2 /* GooglePlayMusicDesktopPlayer.js in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -935,26 +1061,46 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
1CD440581E593DDD0064E0BC /* ShellScript */ = {
|
||||
1C09150923F0208F001EB0E1 /* Run Script - set-version.sh */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script - set-version.sh";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds.\n\"$SRCROOT/../SharedSource/Scripts/set-version.sh\"\n";
|
||||
};
|
||||
1CD440581E593DDD0064E0BC /* Run Script - set-version.sh */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script - set-version.sh";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Append the git HEAD short ID to the build version for snapshot builds. Thanks to\n# Václav Slavík for the initial version of this: http://stackoverflow.com/a/26354117/1091063\n# TODO: Update CFBundleVersion as well?\n\n# If HEAD isn't tagged, or has \"SNAPSHOT\" or \"DEBUG\" in the tag name, this is a snapshot build.\n# If HEAD is tagged more than once, use the most recent.\nTAG=$(/usr/bin/git tag --points-at HEAD --sort='-taggerdate' 2>/dev/null | head -n 1)\nif [[ $? -eq 0 ]] && ( [[ \"${TAG}\" == \"\" ]] || \\\n [[ \"${TAG}\" =~ .*SNAPSHOT.* ]] || \\\n [[ \"${TAG}\" =~ .*DEBUG.* ]] ); then\n HEAD=$(/usr/bin/git rev-list HEAD --max-count=1 --abbrev-commit)\n INFO_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n if [[ \"${CONFIGURATION}\" != \"Release\" ]]; then\n TYPE=\"DEBUG\"\n else\n TYPE=\"SNAPSHOT\"\n fi\n if [[ -f \"$INFO_PLIST\" ]]; then\n CURRENT_VERSION=$(/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"${INFO_PLIST}\")\n BASE_VERSION=$(/usr/libexec/PlistBuddy -c \"Print :BGMBundleVersionBase\" \"${INFO_PLIST}\" 2>/dev/null)\n if [[ $? -ne 0 ]] || [[ \"${BASE_VERSION}\" == \"\" ]]; then\n BASE_VERSION=\"${CURRENT_VERSION}\"\n /usr/libexec/PlistBuddy -c \"Add :BGMBundleVersionBase string ${BASE_VERSION}\" \"${INFO_PLIST}\"\n fi\n NEW_VERSION=\"${BASE_VERSION}-${TYPE}-${HEAD}\"\n if [[ \"${NEW_VERSION}\" != \"${CURRENT_VERSION}\" ]]; then # Only touch the file if we need to.\n /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString ${NEW_VERSION}\" \"${INFO_PLIST}\"\n fi\n fi\nfi\n";
|
||||
shellScript = "# Append the git HEAD short ID to the build version for SNAPSHOT and DEBUG builds.\n\"$SRCROOT/../SharedSource/Scripts/set-version.sh\"\n";
|
||||
};
|
||||
276972891CAFCE91007A2F7C /* ShellScript */ = {
|
||||
276972891CAFCE91007A2F7C /* Run Script - post_install.sh */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 8;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script - post_install.sh";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
@@ -970,9 +1116,11 @@
|
||||
files = (
|
||||
1C86DA6A1F91EE3B000C8CCF /* CAPThread.cpp in Sources */,
|
||||
1C780FF21FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,
|
||||
1CE03A57239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
|
||||
1C4699471BD5C0E400F78043 /* BGMiTunes.m in Sources */,
|
||||
1CD410D41F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,
|
||||
1C1962E41BC94E15008A4DF7 /* CARingBuffer.cpp in Sources */,
|
||||
1C8D830B2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,
|
||||
273F10DF1CC3D0B900C1C6DA /* BGMVOX.m in Sources */,
|
||||
1CC1DF811BE5068A00FB8FE4 /* CACFArray.cpp in Sources */,
|
||||
279F48771DD6D73A00768A85 /* BGMHermes.m in Sources */,
|
||||
@@ -1003,10 +1151,13 @@
|
||||
1CED616C1C316E1A002CAFCF /* BGMAudioDeviceManager.mm in Sources */,
|
||||
2743C9F11D853FBB0089613B /* BGMUserDefaults.m in Sources */,
|
||||
1C1962FD1BCAC0C3008A4DF7 /* CADebugPrintf.cpp in Sources */,
|
||||
1C80DED320A6718600045BBE /* BGMAppWatcher.m in Sources */,
|
||||
2743C9EC1D852B360089613B /* BGMScriptingBridge.m in Sources */,
|
||||
1CC6593C1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
|
||||
1C837DD81F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
|
||||
1C9258472090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,
|
||||
1C1963011BCAC0F6008A4DF7 /* CACFString.cpp in Sources */,
|
||||
9E129A412602AE620005851B /* BGMASApplication.m in Sources */,
|
||||
1C1962E71BC94E91008A4DF7 /* BGMPlayThrough.cpp in Sources */,
|
||||
1C8D8304204238DB00A838F2 /* BGMSwinsian.m in Sources */,
|
||||
1C1962FA1BCAC061008A4DF7 /* CADebugMacros.cpp in Sources */,
|
||||
@@ -1020,6 +1171,9 @@
|
||||
1C1465B81BCC3A73003AEFE6 /* BGMAutoPauseMusic.mm in Sources */,
|
||||
19FE7F77376562C179449013 /* BGMStatusBarItem.mm in Sources */,
|
||||
19FE719951725A698A419CBA /* BGMVolumeChangeListener.cpp in Sources */,
|
||||
19FE72566BCEB11BD1F3D487 /* BGMMusic.m in Sources */,
|
||||
19FE70F73D26D54450779A22 /* BGMPlayThroughRTLogger.cpp in Sources */,
|
||||
19FE7B7BDF0C683288654F90 /* BGMDebugLogging.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1027,10 +1181,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C8104B022AD082E00B35517 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,
|
||||
1C8D830520423E1C00A838F2 /* BGMSwinsian.m in Sources */,
|
||||
1CE03A58239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
|
||||
1CACCF3A1F334447007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,
|
||||
1C780FF31FEF6C3B00497FAD /* BGMSystemSoundsVolume.mm in Sources */,
|
||||
1CC6593D1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
|
||||
1C80DED420A6718600045BBE /* BGMAppWatcher.m in Sources */,
|
||||
1CD410D51F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,
|
||||
1CD989571ECFFD250014BBBF /* CAHostTimeBase.cpp in Sources */,
|
||||
1CD989581ECFFD250014BBBF /* CAMutex.cpp in Sources */,
|
||||
@@ -1047,6 +1204,7 @@
|
||||
1CD989481ECFFCFC0014BBBF /* BGMMusicPlayer.m in Sources */,
|
||||
1CD989491ECFFCFC0014BBBF /* BGMDecibel.m in Sources */,
|
||||
1C3D36731ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,
|
||||
1C9258482090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,
|
||||
1CD9894A1ECFFCFC0014BBBF /* BGMiTunes.m in Sources */,
|
||||
1CD9894B1ECFFCFC0014BBBF /* BGMSpotify.m in Sources */,
|
||||
1CD9894C1ECFFCFC0014BBBF /* BGMHermes.m in Sources */,
|
||||
@@ -1066,6 +1224,7 @@
|
||||
1CD989361ECFFC9E0014BBBF /* CACFDictionary.cpp in Sources */,
|
||||
1CD989371ECFFC9E0014BBBF /* CACFNumber.cpp in Sources */,
|
||||
1CD989381ECFFC9E0014BBBF /* CACFString.cpp in Sources */,
|
||||
9E542C7026057FBA0016C0B5 /* BGMASApplication.m in Sources */,
|
||||
1CD989391ECFFC9E0014BBBF /* CADebugger.cpp in Sources */,
|
||||
1CD9893A1ECFFC9E0014BBBF /* CADebugMacros.cpp in Sources */,
|
||||
1CD9893B1ECFFC9E0014BBBF /* CADebugPrintf.cpp in Sources */,
|
||||
@@ -1079,6 +1238,9 @@
|
||||
1C2FC3151EC706E000A76592 /* BGMAppDelegate+AppleScript.mm in Sources */,
|
||||
19FE7921FD1B6C037429ECA4 /* BGMStatusBarItem.mm in Sources */,
|
||||
19FE7DFF63F69E77C53BF95E /* BGMVolumeChangeListener.cpp in Sources */,
|
||||
19FE7B32E1214BA0E8166A9E /* BGMMusic.m in Sources */,
|
||||
19FE72D66CBC5C39F86333DE /* BGMPlayThroughRTLogger.cpp in Sources */,
|
||||
19FE734C861E0370C21E4E94 /* BGMDebugLogging.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1107,6 +1269,7 @@
|
||||
27D643C01C9FB99200737F6E /* BGMXPCHelperService.mm in Sources */,
|
||||
27D643C11C9FB99200737F6E /* main.m in Sources */,
|
||||
277170161CA24D7C00AB34B4 /* BGMXPCListenerDelegate.m in Sources */,
|
||||
19FE7590D7565E7677D84C55 /* BGMDebugLogging.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1114,23 +1277,27 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1C62FE5123D3EB2E00B9B68E /* Mock_CAHALAudioSystemObject.cpp in Sources */,
|
||||
1C8104AF22AD07E200B35517 /* BGMAppWatcher.m in Sources */,
|
||||
1C8D830620423E2400A838F2 /* BGMSwinsian.m in Sources */,
|
||||
1C227C0B1FA4C48200A95B6D /* BGMAppVolumes.m in Sources */,
|
||||
1CEACF4D1F34793700FEC143 /* CAHALAudioDevice.cpp in Sources */,
|
||||
1CACCF3B1F334450007F86CA /* BGMBackgroundMusicDevice.cpp in Sources */,
|
||||
1C8D830C2042DE9600A838F2 /* BGMGooglePlayMusicDesktopPlayer.m in Sources */,
|
||||
1C3D36741ED90E8600F98E66 /* BGMDeviceControlsList.cpp in Sources */,
|
||||
27FB8C301DE4758A0084DB9D /* BGMPlayThrough.cpp in Sources */,
|
||||
27FB8C311DE4758A0084DB9D /* BGM_Utils.cpp in Sources */,
|
||||
27FB8C071DD75D0A0084DB9D /* BGMHermes.m in Sources */,
|
||||
2743CA211D86DE780089613B /* BGMDeviceControlSync.cpp in Sources */,
|
||||
1CE03A59239B56740036908D /* BGMDebugLoggingMenuItem.m in Sources */,
|
||||
2743CA0C1D86D7FA0089613B /* CACFArray.cpp in Sources */,
|
||||
1C837DDA1F6AA1F2004B1E60 /* BGMOutputVolumeMenuItem.mm in Sources */,
|
||||
2743CA0D1D86D7FA0089613B /* CACFDictionary.cpp in Sources */,
|
||||
2743CA0E1D86D7FA0089613B /* CACFNumber.cpp in Sources */,
|
||||
2743CA0F1D86D7FA0089613B /* CACFString.cpp in Sources */,
|
||||
1CCC4F4E1E581C40008053E4 /* Mock_CAHALAudioObject.cpp in Sources */,
|
||||
2743CA101D86D7FA0089613B /* CADebugger.cpp in Sources */,
|
||||
1C687A6B23B889E000834B75 /* BGMPlayThroughRTLoggerTests.mm in Sources */,
|
||||
2743CA111D86D7FA0089613B /* CADebugMacros.cpp in Sources */,
|
||||
1C62FE4F23D3EB2E00B9B68E /* Mock_CAHALAudioObject.cpp in Sources */,
|
||||
2743CA121D86D7FA0089613B /* CADebugPrintf.cpp in Sources */,
|
||||
1CCC4F4D1E581C40008053E4 /* BGMMusicPlayersUnitTests.mm in Sources */,
|
||||
2743CA141D86D7FA0089613B /* CAHALAudioStream.cpp in Sources */,
|
||||
@@ -1140,7 +1307,6 @@
|
||||
2743CA191D86D7FA0089613B /* CARingBuffer.cpp in Sources */,
|
||||
2743CA0A1D86D52D0089613B /* BGMAudioDeviceManager.mm in Sources */,
|
||||
2743CA031D86D41C0089613B /* BGMScriptingBridge.m in Sources */,
|
||||
1CEACF4F1F34A30000FEC143 /* Mock_CAHALAudioSystemObject.cpp in Sources */,
|
||||
2743CA041D86D41C0089613B /* BGMMusicPlayer.m in Sources */,
|
||||
1CD410D61F9EDDAD0070A094 /* BGMAppVolumesController.mm in Sources */,
|
||||
2743CA051D86D41C0089613B /* BGMDecibel.m in Sources */,
|
||||
@@ -1148,12 +1314,21 @@
|
||||
2743CA071D86D41C0089613B /* BGMVLC.m in Sources */,
|
||||
1CF5423D1EAAEE4300445AD8 /* BGMAudioDevice.cpp in Sources */,
|
||||
2743CA081D86D41C0089613B /* BGMVOX.m in Sources */,
|
||||
1C62FE5223D3EB2E00B9B68E /* Mock_CAHALAudioDevice.cpp in Sources */,
|
||||
1C62FE5323D3EB2E00B9B68E /* MockAudioDevice.cpp in Sources */,
|
||||
2743CA091D86D41C0089613B /* BGMUserDefaults.m in Sources */,
|
||||
1C62FE5023D3EB2E00B9B68E /* MockAudioObjects.cpp in Sources */,
|
||||
1CC6593E1F91DEB400B0CCDC /* BGMTermination.mm in Sources */,
|
||||
2743CA011D86D3CB0089613B /* BGMMusicPlayers.mm in Sources */,
|
||||
1C62FE4E23D3EB2E00B9B68E /* MockAudioObject.cpp in Sources */,
|
||||
2743CA021D86D3CB0089613B /* BGMiTunes.m in Sources */,
|
||||
19FE77608F6C80D0B1F595A7 /* BGMStatusBarItem.mm in Sources */,
|
||||
19FE7071FF5280BC38F35E1D /* BGMVolumeChangeListener.cpp in Sources */,
|
||||
1C9258492090287F00B8D3A6 /* BGMGooglePlayMusicDesktopPlayerConnection.m in Sources */,
|
||||
19FE76F614F260F3F65AF550 /* BGMMusic.m in Sources */,
|
||||
19FE715E7338035C7BCD24E7 /* BGMPlayThroughRTLogger.cpp in Sources */,
|
||||
19FE78EEC6D3C3B19D1FBD64 /* BGMDebugLogging.c in Sources */,
|
||||
19FE7BD48C0CA2CAF16C9ACE /* BGMPlayThroughTests.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1162,6 +1337,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1CCC4F3E1E58196C008053E4 /* BGMXPCHelperTests.m in Sources */,
|
||||
19FE7C144C12607D947EB030 /* BGMDebugLogging.c in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1223,6 +1399,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
@@ -1232,6 +1409,7 @@
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -1265,7 +1443,7 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "-fno-omit-frame-pointer";
|
||||
@@ -1280,6 +1458,8 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_USE_OPTIMIZATION_PROFILE = NO;
|
||||
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEPLOYMENT_POSTPROCESSING = NO;
|
||||
@@ -1291,7 +1471,10 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.App;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_STYLE = "non-global";
|
||||
WARNING_CFLAGS = "-Wpartial-availability";
|
||||
WARNING_CFLAGS = (
|
||||
"-Wpartial-availability",
|
||||
"-Wthread-safety",
|
||||
);
|
||||
};
|
||||
name = DebugOpt;
|
||||
};
|
||||
@@ -1326,6 +1509,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
@@ -1335,6 +1519,7 @@
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c11;
|
||||
@@ -1368,7 +1553,7 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "-fno-omit-frame-pointer";
|
||||
@@ -1409,6 +1594,7 @@
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
|
||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
@@ -1418,6 +1604,7 @@
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c11;
|
||||
@@ -1448,7 +1635,7 @@
|
||||
GCC_WARN_UNUSED_LABEL = YES;
|
||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "";
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
@@ -1462,6 +1649,8 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_USE_OPTIMIZATION_PROFILE = NO;
|
||||
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEPLOYMENT_POSTPROCESSING = NO;
|
||||
@@ -1473,7 +1662,10 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.App;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_STYLE = "non-global";
|
||||
WARNING_CFLAGS = "-Wpartial-availability";
|
||||
WARNING_CFLAGS = (
|
||||
"-Wpartial-availability",
|
||||
"-Wthread-safety",
|
||||
);
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -1482,11 +1674,22 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_USE_OPTIMIZATION_PROFILE = NO;
|
||||
CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = BGMApp/BGMApp.entitlements;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEPLOYMENT_POSTPROCESSING = YES;
|
||||
DWARF_DSYM_FILE_NAME = "$(EXECUTABLE_NAME).dSYM";
|
||||
DWARF_DSYM_FOLDER_PATH = "$(CONFIGURATION_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=0",
|
||||
"CoreAudio_Debug=0",
|
||||
"CoreAudio_StopOnAssert=0",
|
||||
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
|
||||
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
|
||||
"CoreAudio_StopOnThrow=0",
|
||||
"CoreAudio_UseSysLog=1",
|
||||
);
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||
INFOPLIST_FILE = BGMApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
@@ -1497,6 +1700,7 @@
|
||||
WARNING_CFLAGS = (
|
||||
"-Wno-profile-instr-out-of-date",
|
||||
"-Wpartial-availability",
|
||||
"-Wthread-safety",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
@@ -1504,14 +1708,16 @@
|
||||
1CCC4F5C1E584081008053E4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TEST_TARGET_NAME = "Background Music";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
@@ -1520,13 +1726,15 @@
|
||||
1CCC4F5D1E584081008053E4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TEST_TARGET_NAME = "Background Music";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
@@ -1535,14 +1743,16 @@
|
||||
1CCC4F5E1E584081008053E4 /* DebugOpt */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "BGMApp/BGMApp-Debug.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = "BGMAppTests/UITests/BGMAppUITests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
TEST_TARGET_NAME = "Background Music";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
@@ -1551,6 +1761,7 @@
|
||||
27379B991C7F57DB0084A24C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEPLOYMENT_POSTPROCESSING = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -1564,12 +1775,14 @@
|
||||
INSTALL_PATH = /usr/local/libexec;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;
|
||||
PRODUCT_NAME = BGMXPCHelper;
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
27379B9A1C7F57DB0084A24C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEPLOYMENT_POSTPROCESSING = YES;
|
||||
INFOPLIST_FILE = BGMXPCHelper/Info.plist;
|
||||
INSTALL_GROUP = wheel;
|
||||
@@ -1578,12 +1791,14 @@
|
||||
LLVM_LTO = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;
|
||||
PRODUCT_NAME = BGMXPCHelper;
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
27379B9B1C7F57DB0084A24C /* DebugOpt */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEPLOYMENT_POSTPROCESSING = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -1597,6 +1812,7 @@
|
||||
INSTALL_PATH = /usr/local/libexec;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.XPCHelper;
|
||||
PRODUCT_NAME = BGMXPCHelper;
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
};
|
||||
name = DebugOpt;
|
||||
};
|
||||
@@ -1608,11 +1824,25 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"CoreAudio_Debug=1",
|
||||
"CoreAudio_UseSysLog=1",
|
||||
"CoreAudio_StopOnAssert=1",
|
||||
"CoreAudio_ThreadStampMessages=0",
|
||||
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
|
||||
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
|
||||
"CoreAudio_StopOnThrow=0",
|
||||
"BGM_UnitTest=1",
|
||||
);
|
||||
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -1625,11 +1855,23 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=0",
|
||||
"CoreAudio_Debug=0",
|
||||
"CoreAudio_StopOnAssert=0",
|
||||
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
|
||||
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
|
||||
"CoreAudio_StopOnThrow=0",
|
||||
"BGM_UnitTest=1",
|
||||
);
|
||||
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
name = Release;
|
||||
@@ -1642,11 +1884,25 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = NO;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"CoreAudio_Debug=1",
|
||||
"CoreAudio_UseSysLog=1",
|
||||
"CoreAudio_StopOnAssert=1",
|
||||
"CoreAudio_ThreadStampMessages=0",
|
||||
"BGM_StopDebuggerOnLoggedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_EXCEPTIONS)",
|
||||
"BGM_StopDebuggerOnLoggedUnexpectedExceptions=$(BGM_STOP_DEBUGGER_ON_LOGGED_UNEXPECTED_EXCEPTIONS)",
|
||||
"CoreAudio_StopOnThrow=0",
|
||||
"BGM_UnitTest=1",
|
||||
);
|
||||
INFOPLIST_FILE = "BGMAppTests/UnitTests/BGMAppUnitTests-Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bearisdriving.BGM.AppUnitTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
WARNING_CFLAGS = "";
|
||||
};
|
||||
name = DebugOpt;
|
||||
|
||||
@@ -26,9 +26,42 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
enableAddressSanitizer = "YES"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
enableUBSanitizer = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
|
||||
BuildableName = "BGMXPCHelper.xpc"
|
||||
BlueprintName = "BGMXPCHelper"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "MallocScribble"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
|
||||
BuildableName = "BGMXPCHelper.xpc"
|
||||
BlueprintName = "BGMXPCHelper"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -41,24 +74,14 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "27379B8E1C7F57DA0084A24C"
|
||||
BuildableName = "BGMXPCHelper.xpc"
|
||||
BlueprintName = "BGMXPCHelper"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
language = ""
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
enableUBSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@@ -82,6 +105,16 @@
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "MallocScribble"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
|
||||
@@ -26,11 +26,47 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
enableAddressSanitizer = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
enableUBSanitizer = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
|
||||
BuildableName = "Background Music.app"
|
||||
BlueprintName = "Background Music"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "MallocScribble"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
|
||||
BuildableName = "Background Music.app"
|
||||
BlueprintName = "Background Music"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</CodeCoverageTargets>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
skipped = "NO"
|
||||
parallelizable = "YES"
|
||||
testExecutionOrdering = "random">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2743C9F51D86CFF90089613B"
|
||||
@@ -50,23 +86,14 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "1CB8B3351BBA75EF000E2DD1"
|
||||
BuildableName = "Background Music.app"
|
||||
BlueprintName = "Background Music"
|
||||
ReferencedContainer = "container:BGMApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
enableUBSanitizer = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@@ -101,6 +128,16 @@
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "MallocScribble"
|
||||
value = ""
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<!--
|
||||
Without this key, AddressSanitizer and the UI tests would only work when BGMApp is code signed.
|
||||
-->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>com.apple.security.automation.apple-events</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -17,17 +17,17 @@
|
||||
// BGMAppDelegate.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016, 2017, 2020 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
// Sets up and tears down the app.
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class BGMAudioDeviceManager;
|
||||
@class BGMAppVolumesController;
|
||||
|
||||
// Tags for UI elements in MainMenu.xib
|
||||
static NSInteger const kVolumesHeadingMenuItemTag = 3;
|
||||
@@ -50,8 +50,10 @@ static NSInteger const kSeparatorBelowVolumesMenuItemTag = 4;
|
||||
@property (unsafe_unretained) IBOutlet NSTextView* aboutPanelLicenseView;
|
||||
|
||||
@property (weak) IBOutlet NSMenuItem* autoPauseMenuItemUnwrapped;
|
||||
@property (weak) IBOutlet NSMenuItem* debugLoggingMenuItemUnwrapped;
|
||||
|
||||
@property (readonly) BGMAudioDeviceManager* audioDevices;
|
||||
@property BGMAppVolumesController* appVolumes;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+125
-52
@@ -17,7 +17,8 @@
|
||||
// BGMAppDelegate.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
// Copyright © 2016-2022 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -25,9 +26,11 @@
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMAppVolumes.h"
|
||||
#import "BGMAppVolumesController.h"
|
||||
#import "BGMAutoPauseMusic.h"
|
||||
#import "BGMAutoPauseMenuItem.h"
|
||||
#import "BGMDebugLoggingMenuItem.h"
|
||||
#import "BGMMusicPlayers.h"
|
||||
#import "BGMOutputDeviceMenuSection.h"
|
||||
#import "BGMOutputVolumeMenuItem.h"
|
||||
@@ -63,14 +66,15 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||
BGMAutoPauseMenuItem* autoPauseMenuItem;
|
||||
BGMMusicPlayers* musicPlayers;
|
||||
BGMSystemSoundsVolume* systemSoundsVolume;
|
||||
BGMAppVolumesController* appVolumes;
|
||||
BGMOutputDeviceMenuSection* outputDeviceMenuSection;
|
||||
BGMPreferencesMenu* prefsMenu;
|
||||
BGMDebugLoggingMenuItem* debugLoggingMenuItem;
|
||||
BGMXPCListener* xpcListener;
|
||||
BGMPreferredOutputDevices* preferredOutputDevices;
|
||||
}
|
||||
|
||||
@synthesize audioDevices = audioDevices;
|
||||
@synthesize appVolumes = appVolumes;
|
||||
|
||||
- (void) awakeFromNib {
|
||||
[super awakeFromNib];
|
||||
@@ -115,6 +119,48 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||
preferredOutputDevices =
|
||||
[[BGMPreferredOutputDevices alloc] initWithDevices:audioDevices userDefaults:userDefaults];
|
||||
|
||||
// Skip this if we're compiling on a version of macOS before 10.14 as won't compile and it
|
||||
// isn't needed.
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 // MAC_OS_X_VERSION_10_14
|
||||
if (@available(macOS 10.14, *)) {
|
||||
// On macOS 10.14+ we need to get the user's permission to use input devices before we can
|
||||
// use BGMDevice for playthrough (see BGMPlayThrough), so we wait until they've given it
|
||||
// before making BGMDevice the default device. This way, if the user is playing audio when
|
||||
// they open Background Music, we won't interrupt it while we're waiting for them to click
|
||||
// OK.
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
|
||||
completionHandler:^(BOOL granted) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (granted) {
|
||||
DebugMsg("BGMAppDelegate::applicationDidFinishLaunching: Permission granted");
|
||||
[self continueLaunchAfterInputDevicePermissionGranted];
|
||||
} else {
|
||||
NSLog(@"BGMAppDelegate::applicationDidFinishLaunching: Permission denied");
|
||||
// If they don't accept, Background Music won't work at all and the only way to
|
||||
// fix it is in System Preferences, so show an error dialog with instructions.
|
||||
//
|
||||
// TODO: It would be nice if this dialog had a shortcut to open the System
|
||||
// Preferences panel. See showSetDeviceAsDefaultError.
|
||||
[self showErrorMessage:@"Background Music needs permission to use microphones."
|
||||
informativeText:@"It uses a virtual microphone to access your system's "
|
||||
"audio.\n\nYou can grant the permission by going to "
|
||||
"System Preferences > Security and Privacy > "
|
||||
"Microphone and checking the box for Background Music."
|
||||
exitAfterMessageDismissed:YES];
|
||||
}
|
||||
});
|
||||
}];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// We can change the device immediately on older versions of macOS because they don't
|
||||
// require user permission for input devices.
|
||||
[self continueLaunchAfterInputDevicePermissionGranted];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) continueLaunchAfterInputDevicePermissionGranted {
|
||||
// Choose an output device for BGMApp to use to play audio.
|
||||
if (![self setInitialOutputDevice]) {
|
||||
return;
|
||||
@@ -132,16 +178,16 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||
|
||||
autoPauseMusic = [[BGMAutoPauseMusic alloc] initWithAudioDevices:audioDevices
|
||||
musicPlayers:musicPlayers];
|
||||
|
||||
|
||||
[self setUpMainMenu];
|
||||
|
||||
|
||||
xpcListener = [[BGMXPCListener alloc] initWithAudioDevices:audioDevices
|
||||
helperConnectionErrorHandler:^(NSError* error) {
|
||||
NSLog(@"BGMAppDelegate::applicationDidFinishLaunching: (helperConnectionErrorHandler) "
|
||||
"BGMXPCHelper connection error: %@",
|
||||
error);
|
||||
[self showXPCHelperErrorMessage:error];
|
||||
}];
|
||||
NSLog(@"BGMAppDelegate::continueLaunchAfterInputDevicePermissionGranted: "
|
||||
"(helperConnectionErrorHandler) BGMXPCHelper connection error: %@",
|
||||
error);
|
||||
[self showXPCHelperErrorMessage:error];
|
||||
}];
|
||||
}
|
||||
|
||||
// Returns NO if (and only if) BGMApp is about to terminate because of a fatal error.
|
||||
@@ -179,51 +225,73 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||
|
||||
// Sets the "Background Music" virtual audio device (BGMDevice) as the user's default audio device.
|
||||
- (void) setBGMDeviceAsDefault {
|
||||
void (^setDefaultDevice)() = ^{
|
||||
NSError* error = [audioDevices setBGMDeviceAsOSDefault];
|
||||
NSError* error = [audioDevices setBGMDeviceAsOSDefault];
|
||||
|
||||
if (error) {
|
||||
[self showSetDeviceAsDefaultError:error
|
||||
message:@"Could not set the Background Music device as your"
|
||||
"default audio device."
|
||||
informativeText:@"You might be able to change it yourself."];
|
||||
}
|
||||
};
|
||||
|
||||
// Skip this if we're compiling on a version of macOS before 10.14 as won't compile and it
|
||||
// isn't needed.
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 // MAC_OS_X_VERSION_10_14
|
||||
if (@available(macOS 10.14, *)) {
|
||||
// On macOS 10.14+ we need to get the user's permission to use input devices before we can
|
||||
// use BGMDevice for playthrough (see BGMPlayThrough), so we wait until they've given it
|
||||
// before making BGMDevice the default device. This way, if the user is playing audio when
|
||||
// they open Background Music, we won't interrupt it while we're waiting for them to click
|
||||
// OK.
|
||||
//
|
||||
// TODO: This isn't a perfect solution because, if the user takes too long to accept,
|
||||
// BGMPlayThrough will try to use BGMDevice again and log some errors.
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
|
||||
completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
DebugMsg("BGMAppDelegate::setBGMDeviceAsDefault: "
|
||||
"Permission granted");
|
||||
setDefaultDevice();
|
||||
} else {
|
||||
NSLog(@"BGMAppDelegate::setBGMDeviceAsDefault: "
|
||||
"Permission denied");
|
||||
// TODO: If they don't accept, Background Music won't work
|
||||
// at all and the only way to fix it is in System
|
||||
// Preferences, so we should show an error dialog
|
||||
// with instructions.
|
||||
}
|
||||
}];
|
||||
if (error) {
|
||||
[self showSetDeviceAsDefaultError:error
|
||||
message:@"Could not set the Background Music device as your"
|
||||
"default audio device."
|
||||
informativeText:@"You might be able to change it yourself."];
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// We can change the device immediately on older versions of macOS because they don't
|
||||
// require user permission for input devices.
|
||||
setDefaultDevice();
|
||||
}
|
||||
|
||||
- (void) menuWillOpen:(NSMenu*)menu {
|
||||
if (@available(macOS 10.16, *)) {
|
||||
// Set menu offset and check for any active menu items
|
||||
float menuOffset = 12.0;
|
||||
for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {
|
||||
if (menuItem.state == NSControlStateValueOn && menuItem.indentationLevel == 0) {
|
||||
menuOffset += 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Align volume output device and slider
|
||||
for (NSView* subview in self.outputVolumeView.subviews) {
|
||||
CGRect newSubview = subview.frame;
|
||||
newSubview.origin.x = menuOffset;
|
||||
subview.frame = newSubview;
|
||||
}
|
||||
|
||||
// Align system sounds and app volumes
|
||||
double appIconTitleOffset = 0;
|
||||
for (NSMenuItem* menuItem in self.bgmMenu.itemArray) {
|
||||
if (menuItem.view.subviews.count == 7 || menuItem.view.subviews.count == 3) {
|
||||
NSTextField* appTitle;
|
||||
NSImageView* appIcon;
|
||||
|
||||
for (NSView* subview in menuItem.view.subviews) {
|
||||
if (menuItem.view.subviews.count == 3) {
|
||||
// System sounds
|
||||
if ([subview isKindOfClass:[NSTextField class]]) {
|
||||
appTitle = (NSTextField*)subview;
|
||||
}
|
||||
if ([subview isKindOfClass:[NSImageView class]]) {
|
||||
appIcon = (NSImageView*)subview;
|
||||
}
|
||||
} else if (menuItem.view.subviews.count == 7) {
|
||||
// App volumes
|
||||
if ([subview isKindOfClass:[BGMAVM_AppNameLabel class]]) {
|
||||
appTitle = (NSTextField*)subview;
|
||||
}
|
||||
if ([subview isKindOfClass:[BGMAVM_AppIcon class]]) {
|
||||
appIcon = (NSImageView*)subview;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appIconTitleOffset == 0) {
|
||||
appIconTitleOffset = appTitle.frame.origin.x - appIcon.frame.origin.x;
|
||||
}
|
||||
|
||||
CGRect newAppIcon = appIcon.frame;
|
||||
newAppIcon.origin.x = menuOffset;
|
||||
appIcon.frame = newAppIcon;
|
||||
CGRect newAppTitle = appTitle.frame;
|
||||
newAppTitle.origin.x = menuOffset + appIconTitleOffset;
|
||||
appTitle.frame = newAppTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,6 +319,11 @@ static NSString* const kOptShowDockIcon = @"--show-dock-icon";
|
||||
aboutPanel:self.aboutPanel
|
||||
aboutPanelLicenseView:self.aboutPanelLicenseView];
|
||||
|
||||
// Enable/disable debug logging. Hidden unless you option-click the status bar icon.
|
||||
debugLoggingMenuItem =
|
||||
[[BGMDebugLoggingMenuItem alloc] initWithMenuItem:self.debugLoggingMenuItemUnwrapped];
|
||||
[statusBarItem setDebugLoggingMenuItem:debugLoggingMenuItem];
|
||||
|
||||
// Handle events about the main menu. (See the NSMenuDelegate methods below.)
|
||||
self.bgmMenu.delegate = self;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
@@ -35,7 +36,7 @@
|
||||
bgmMenu:(NSMenu*)inMenu
|
||||
appVolumeView:(NSView*)inView;
|
||||
|
||||
// Pass -1 for initialVolume or initialPan to leave the volume/pan at its default level.
|
||||
// Pass -1 for initialVolume or kAppPanNoValue for initialPan to leave the volume/pan at its default level.
|
||||
- (void) insertMenuItemForApp:(NSRunningApplication*)app
|
||||
initialVolume:(int)volume
|
||||
initialPan:(int)pan;
|
||||
@@ -44,6 +45,9 @@
|
||||
|
||||
- (void) removeAllAppVolumeMenuItems;
|
||||
|
||||
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app;
|
||||
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;
|
||||
|
||||
@end
|
||||
|
||||
// Protocol for the UI custom classes
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
// BGMAppVolumes.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2020 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
// Copyright © 2021 Marcus Wu
|
||||
// Copyright © 2022 Jon Egan
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -124,6 +126,79 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMenuItem*) getMenuItemForApp:(NSRunningApplication*)app {
|
||||
NSInteger lastAppVolumeMenuItemIndex = [self lastMenuItemIndex] - 2;
|
||||
|
||||
for (NSInteger i = [self firstMenuItemIndex]; i <= lastAppVolumeMenuItemIndex; i++) {
|
||||
NSMenuItem* item = [bgmMenu itemAtIndex:i];
|
||||
NSRunningApplication* itemApp = item.representedObject;
|
||||
BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);
|
||||
|
||||
if ([itemApp isEqual:app]) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
for (NSInteger i = 0; i < [moreAppsMenu numberOfItems]; i++) {
|
||||
NSMenuItem* item = [moreAppsMenu itemAtIndex:i];
|
||||
NSRunningApplication* itemApp = item.representedObject;
|
||||
BGMAssert(itemApp, "!itemApp for %s", item.title.UTF8String);
|
||||
|
||||
if ([itemApp isEqual:app]) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app {
|
||||
BGMAppVolumeAndPan result = {
|
||||
.volume = -1,
|
||||
.pan = kAppPanNoValue
|
||||
};
|
||||
|
||||
NSMenuItem *item = [self getMenuItemForApp:app];
|
||||
|
||||
if (item == nil) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (NSView* subview in item.view.subviews) {
|
||||
// Get the volume.
|
||||
if ([subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
|
||||
result.volume = [(BGMAVM_VolumeSlider*)subview intValue];
|
||||
}
|
||||
|
||||
// Get the pan position.
|
||||
if ([subview isKindOfClass:[BGMAVM_PanSlider class]]) {
|
||||
result.pan = [(BGMAVM_PanSlider*)subview intValue];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
|
||||
NSMenuItem *item = [self getMenuItemForApp:app];
|
||||
|
||||
if (item == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSView* subview in item.view.subviews) {
|
||||
// Set the volume.
|
||||
if (volumeAndPan.volume != -1 && [subview isKindOfClass:[BGMAVM_VolumeSlider class]]) {
|
||||
[(BGMAVM_VolumeSlider*)subview setRelativeVolume:volumeAndPan.volume];
|
||||
}
|
||||
|
||||
// Set the pan position.
|
||||
if (volumeAndPan.pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
|
||||
[(BGMAVM_PanSlider*)subview setPanPosition:volumeAndPan.pan];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a blank menu item to copy as a template.
|
||||
- (NSMenuItem*) createBlankAppVolumeMenuItem {
|
||||
NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
|
||||
@@ -143,7 +218,7 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
||||
}
|
||||
|
||||
// Set the pan position.
|
||||
if (pan != -1 && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
|
||||
if (pan != kAppPanNoValue && [subview isKindOfClass:[BGMAVM_PanSlider class]]) {
|
||||
[(BGMAVM_PanSlider*)subview setPanPosition:pan];
|
||||
}
|
||||
}
|
||||
@@ -196,20 +271,32 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
||||
|
||||
BGMAssert(button, "!button");
|
||||
BGMAssert(menuItem, "!menuItem");
|
||||
|
||||
|
||||
CGFloat width = menuItem.view.frame.size.width;
|
||||
CGFloat height = menuItem.view.frame.size.height;
|
||||
|
||||
#if DEBUG
|
||||
const char* appName = [((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];
|
||||
CGFloat height = menuItem.view.frame.size.height;
|
||||
#endif
|
||||
|
||||
const char* appName =
|
||||
[((NSRunningApplication*)menuItem.representedObject).localizedName UTF8String];
|
||||
|
||||
// Using this function (instead of just ==) shouldn't be necessary, but just in case.
|
||||
#if DEBUG
|
||||
BOOL(^nearEnough)(CGFloat x, CGFloat y) = ^BOOL(CGFloat x, CGFloat y) {
|
||||
return fabs(x - y) < 0.01; // We don't need much precision.
|
||||
};
|
||||
#endif
|
||||
|
||||
if (nearEnough(button.frameCenterRotation, 0.0)) {
|
||||
bool allSubviewsShowing = true;
|
||||
for (NSView* subview in menuItem.view.subviews) {
|
||||
if (subview.hidden) {
|
||||
allSubviewsShowing = false;
|
||||
break;
|
||||
}
|
||||
//DebugMsg("BGMAppVolumes:: subview hash / hidden: (%lu) / (%hhd)", (unsigned long)subview.hash, subview.hidden);
|
||||
}
|
||||
|
||||
if (allSubviewsShowing) {
|
||||
// Hide extra controls
|
||||
DebugMsg("BGMAppVolumes::showHideExtraControls: Hiding extra controls (%s)", appName);
|
||||
|
||||
@@ -241,7 +328,7 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
||||
menuItem.view.frameSize = NSMakeSize(width, appVolumeViewFullHeight);
|
||||
// Turn the button rightside up so the arrowhead points up.
|
||||
button.frameCenterRotation = 0.0;
|
||||
// Move the button down slightly, back to it's original position.
|
||||
// Move the button down slightly, back to its original position.
|
||||
[button setFrameOrigin:NSMakePoint(button.frame.origin.x, button.frame.origin.y + 1)];
|
||||
|
||||
// Set all of the UI elements in the menu item to "not hidden" for accessibility clients.
|
||||
@@ -446,4 +533,3 @@ static NSString* const kMoreAppsMenuTitle = @"More Apps";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
@@ -29,6 +30,11 @@
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
typedef struct BGMAppVolumeAndPan {
|
||||
int volume;
|
||||
int pan;
|
||||
} BGMAppVolumeAndPan;
|
||||
|
||||
@interface BGMAppVolumesController : NSObject
|
||||
|
||||
- (id) initWithMenu:(NSMenu*)menu
|
||||
@@ -45,6 +51,9 @@ forAppWithProcessID:(pid_t)processID
|
||||
forAppWithProcessID:(pid_t)processID
|
||||
bundleID:(NSString* __nullable)bundleID;
|
||||
|
||||
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app;
|
||||
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
//
|
||||
// Copyright © 2017, 2018 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -40,11 +41,6 @@
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
typedef struct BGMAppVolumeAndPan {
|
||||
int volume;
|
||||
int pan;
|
||||
} BGMAppVolumeAndPan;
|
||||
|
||||
@implementation BGMAppVolumesController {
|
||||
// The App Volumes UI.
|
||||
BGMAppVolumes* appVolumes;
|
||||
@@ -104,11 +100,25 @@ typedef struct BGMAppVolumeAndPan {
|
||||
}
|
||||
}
|
||||
|
||||
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication *)app {
|
||||
return [appVolumes getVolumeAndPanForApp:app];
|
||||
}
|
||||
|
||||
- (void) setVolumeAndPan:(BGMAppVolumeAndPan)volumeAndPan forApp:(NSRunningApplication*)app {
|
||||
[appVolumes setVolumeAndPan:volumeAndPan forApp:app];
|
||||
if (volumeAndPan.volume != -1) {
|
||||
[self setVolume:volumeAndPan.volume forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
|
||||
}
|
||||
if (volumeAndPan.pan != kAppPanNoValue) {
|
||||
[self setPanPosition:volumeAndPan.pan forAppWithProcessID:app.processIdentifier bundleID:app.bundleIdentifier];
|
||||
}
|
||||
}
|
||||
|
||||
- (BGMAppVolumeAndPan) getVolumeAndPanForApp:(NSRunningApplication*)app
|
||||
fromVolumes:(const CACFArray&)volumes {
|
||||
BGMAppVolumeAndPan volumeAndPan = {
|
||||
.volume = -1,
|
||||
.pan = -1
|
||||
.pan = kAppPanNoValue
|
||||
};
|
||||
|
||||
for (UInt32 i = 0; i < volumes.GetNumberItems(); i++) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMAppWatcher.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
// Calls callback functions when a given application is launched or terminated. Starts watching
|
||||
// after being initialised, stops after being destroyed.
|
||||
//
|
||||
|
||||
// System Includes
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@interface BGMAppWatcher : NSObject
|
||||
|
||||
// appLaunched will be called when the application is launched and appTerminated will be called when
|
||||
// it's terminated. Background apps, status bar apps, etc. are ignored.
|
||||
- (instancetype) initWithBundleID:(NSString*)bundleID
|
||||
appLaunched:(void(^)(void))appLaunched
|
||||
appTerminated:(void(^)(void))appTerminated;
|
||||
|
||||
// With this constructor, when an application is launched or terminated, isMatchingBundleID will be
|
||||
// called first to decide whether or not the callback should be called.
|
||||
- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched
|
||||
appTerminated:(void(^)(void))appTerminated
|
||||
isMatchingBundleID:(BOOL(^)(NSString* appBundleID))isMatchingBundleID;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMAppWatcher.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMAppWatcher.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@implementation BGMAppWatcher {
|
||||
// Tokens for the notification observers so we can remove them in dealloc.
|
||||
id<NSObject> didLaunchToken;
|
||||
id<NSObject> didTerminateToken;
|
||||
}
|
||||
|
||||
- (instancetype) initWithBundleID:(NSString*)bundleID
|
||||
appLaunched:(void(^)(void))appLaunched
|
||||
appTerminated:(void(^)(void))appTerminated {
|
||||
return [self initWithAppLaunched:appLaunched
|
||||
appTerminated:appTerminated
|
||||
isMatchingBundleID:^BOOL(NSString* appBundleID) {
|
||||
return [bundleID isEqualToString:appBundleID];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype) initWithAppLaunched:(void(^)(void))appLaunched
|
||||
appTerminated:(void(^)(void))appTerminated
|
||||
isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;
|
||||
|
||||
didLaunchToken =
|
||||
[center addObserverForName:NSWorkspaceDidLaunchApplicationNotification
|
||||
object:nil
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification* notification) {
|
||||
if ([BGMAppWatcher shouldBeHandled:notification
|
||||
isMatchingBundleID:isMatchingBundleID]) {
|
||||
appLaunched();
|
||||
}
|
||||
}];
|
||||
|
||||
didTerminateToken =
|
||||
[center addObserverForName:NSWorkspaceDidTerminateApplicationNotification
|
||||
object:nil
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification* notification) {
|
||||
if ([BGMAppWatcher shouldBeHandled:notification
|
||||
isMatchingBundleID:isMatchingBundleID]) {
|
||||
appTerminated();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Returns YES if we should call the app launch/termination callback for this NSNotification.
|
||||
+ (BOOL) shouldBeHandled:(NSNotification*)notification
|
||||
isMatchingBundleID:(BOOL(^)(NSString*))isMatchingBundleID {
|
||||
NSString* __nullable notifiedBundleID =
|
||||
[notification.userInfo[NSWorkspaceApplicationKey] bundleIdentifier];
|
||||
|
||||
// Ignore the notification if the app doesn't have a bundle ID or isMatchingBundleID returns NO.
|
||||
return notifiedBundleID && isMatchingBundleID((NSString*)notifiedBundleID);
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
// Remove the application launch/termination observers.
|
||||
NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;
|
||||
|
||||
if (didLaunchToken) {
|
||||
[center removeObserver:didLaunchToken];
|
||||
didLaunchToken = nil;
|
||||
}
|
||||
|
||||
if (didTerminateToken) {
|
||||
[center removeObserver:didTerminateToken];
|
||||
didTerminateToken = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -48,7 +48,7 @@ BGMAudioDevice::BGMAudioDevice(const CAHALAudioDevice& inDevice)
|
||||
:
|
||||
BGMAudioDevice(inDevice.GetObjectID())
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
BGMAudioDevice::~BGMAudioDevice()
|
||||
{
|
||||
@@ -57,6 +57,8 @@ BGMAudioDevice::~BGMAudioDevice()
|
||||
bool BGMAudioDevice::CanBeOutputDeviceInBGMApp() const
|
||||
{
|
||||
CFStringRef uid = CopyDeviceUID();
|
||||
assert(uid != nullptr);
|
||||
|
||||
bool isNullDevice = CFEqual(uid, CFSTR(kBGMNullDeviceUID));
|
||||
CFRelease(uid);
|
||||
|
||||
@@ -81,7 +83,7 @@ bool BGMAudioDevice::HasSettableMasterVolume(AudioObjectPropertyScope inScope
|
||||
bool BGMAudioDevice::HasSettableVirtualMasterVolume(AudioObjectPropertyScope inScope) const
|
||||
{
|
||||
AudioObjectPropertyAddress virtualMasterVolumeAddress = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
inScope,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
@@ -220,7 +222,7 @@ bool BGMAudioDevice::GetVirtualMasterVolumeScalar(AudioObjectPropertyScope in
|
||||
Float32& outVirtualMasterVolume) const
|
||||
{
|
||||
AudioObjectPropertyAddress virtualMasterVolumeAddress = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
inScope,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
@@ -265,7 +267,7 @@ bool BGMAudioDevice::SetVirtualMasterVolumeScalar(AudioObjectPropertyScope in
|
||||
bool didGetVirtualMasterBalance = GetVirtualMasterBalance(inScope, virtualMasterBalance);
|
||||
|
||||
AudioObjectPropertyAddress virtualMasterVolumeAddress = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterVolume,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainVolume,
|
||||
inScope,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
@@ -277,7 +279,7 @@ bool BGMAudioDevice::SetVirtualMasterVolumeScalar(AudioObjectPropertyScope in
|
||||
|
||||
// Reset the balance
|
||||
AudioObjectPropertyAddress virtualMasterBalanceAddress = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterBalance,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainBalance,
|
||||
inScope,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
@@ -310,7 +312,7 @@ bool BGMAudioDevice::GetVirtualMasterBalance(AudioObjectPropertyScope inScope
|
||||
Float32& outVirtualMasterBalance) const
|
||||
{
|
||||
AudioObjectPropertyAddress virtualMasterBalanceAddress = {
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMasterBalance,
|
||||
kAudioHardwareServiceDeviceProperty_VirtualMainBalance,
|
||||
inScope,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
@@ -340,6 +342,9 @@ bool BGMAudioDevice::IsBGMDevice(bool inIncludeUISoundsInstance) const
|
||||
{
|
||||
// Check the device's UID to see whether it's BGMDevice.
|
||||
CFStringRef uid = CopyDeviceUID();
|
||||
if (uid == nullptr) {
|
||||
return isBGMDevice;
|
||||
}
|
||||
|
||||
isBGMDevice =
|
||||
CFEqual(uid, CFSTR(kBGMDeviceUID)) ||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// BGMAudioDevice.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Copyright © 2017, 2020 Kyle Neideck
|
||||
//
|
||||
// A HAL audio device. Note that this class's only state is the AudioObjectID of the device.
|
||||
//
|
||||
@@ -59,7 +59,8 @@ public:
|
||||
operator AudioObjectID() const { return GetObjectID(); }
|
||||
|
||||
/*!
|
||||
@return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice.)
|
||||
@return True if this device is BGMDevice. (Specifically, the main instance of BGMDevice, not
|
||||
the instance used for UI sounds.)
|
||||
@throws CAException If the HAL returns an error when queried.
|
||||
*/
|
||||
bool IsBGMDevice() const { return IsBGMDevice(false); };
|
||||
|
||||
@@ -378,8 +378,14 @@
|
||||
|
||||
@try {
|
||||
gotLock = [stateLock tryLock];
|
||||
|
||||
if (gotLock) {
|
||||
|
||||
BOOL isBigSur = NO;
|
||||
if (@available(macOS 11.0, *)) {
|
||||
isBigSur = YES;
|
||||
}
|
||||
|
||||
// Always start playthrough asynchronously. Temp workaround for deadlock on Big Sur.
|
||||
if (!isBigSur && gotLock) {
|
||||
BGMPlayThrough& pt = (forUISoundsDevice ? playThrough_UISounds : playThrough);
|
||||
|
||||
// Playthrough might not have been notified that BGMDevice is starting yet, so make sure
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMAutoPauseMenuItem.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2019 Kyle Neideck
|
||||
// Copyright © 2016 Tanner Hoke
|
||||
//
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#import "BGMAutoPauseMenuItem.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGMMusicPlayer.h"
|
||||
#import "BGMAppWatcher.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
@@ -41,7 +41,7 @@ static SInt64 const kMenuItemUpdateWaitTime = 1;
|
||||
NSMenuItem* menuItem;
|
||||
BGMAutoPauseMusic* autoPauseMusic;
|
||||
BGMMusicPlayers* musicPlayers;
|
||||
id<NSObject> didLaunchToken, didTerminateToken;
|
||||
BGMAppWatcher* appWatcher;
|
||||
}
|
||||
|
||||
- (instancetype) initWithMenuItem:(NSMenuItem*)item
|
||||
@@ -66,53 +66,39 @@ static SInt64 const kMenuItemUpdateWaitTime = 1;
|
||||
// Toggle auto-pause when the menu item is clicked.
|
||||
menuItem.target = self;
|
||||
menuItem.action = @selector(toggleAutoPauseMusic);
|
||||
|
||||
[self updateMenuItemTitle];
|
||||
[self initMusicPlayerObservers];
|
||||
|
||||
[self initMenuItemTitle];
|
||||
}
|
||||
|
||||
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];
|
||||
BOOL isAboutThisMusicPlayer = musicPlayers.selectedMusicPlayer.bundleID &&
|
||||
[appBundleID isEqualToString:(NSString*)musicPlayers.selectedMusicPlayer.bundleID];
|
||||
|
||||
if (isAboutThisMusicPlayer) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
||||
kMenuItemUpdateWaitTime * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(),
|
||||
^{
|
||||
[self updateMenuItemTitle];
|
||||
});
|
||||
}
|
||||
}];
|
||||
};
|
||||
|
||||
didLaunchToken = addObserver(NSWorkspaceDidLaunchApplicationNotification);
|
||||
didTerminateToken = addObserver(NSWorkspaceDidTerminateApplicationNotification);
|
||||
}
|
||||
- (void) initMenuItemTitle {
|
||||
// Set the initial text, tool-tip, state, etc.
|
||||
[self updateMenuItemTitle];
|
||||
|
||||
- (void) dealloc {
|
||||
// Remove the application launch/termination observers.
|
||||
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
|
||||
if (didLaunchToken) {
|
||||
[center removeObserver:didLaunchToken];
|
||||
}
|
||||
|
||||
if (didTerminateToken) {
|
||||
[center removeObserver:didTerminateToken];
|
||||
}
|
||||
// Avoid retain cycles in case we ever want to destroy instances of this class.
|
||||
BGMAutoPauseMenuItem* __weak weakSelf = self;
|
||||
|
||||
// Add a callback that enables/disables the Auto-pause Music menu item when the music player
|
||||
// is launched/terminated.
|
||||
void (^callback)(void) = ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMenuItemUpdateWaitTime * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(),
|
||||
^{
|
||||
BGMAutoPauseMenuItem* strongSelf = weakSelf;
|
||||
[strongSelf updateMenuItemTitle];
|
||||
});
|
||||
};
|
||||
|
||||
appWatcher = [[BGMAppWatcher alloc] initWithAppLaunched:callback
|
||||
appTerminated:callback
|
||||
isMatchingBundleID:^BOOL(NSString* appBundleID) {
|
||||
BGMAutoPauseMenuItem* strongSelf = weakSelf;
|
||||
NSString* __nullable playerBundleID =
|
||||
strongSelf->musicPlayers.selectedMusicPlayer.bundleID;
|
||||
return playerBundleID && [appBundleID isEqualToString:(NSString*)playerBundleID];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) toggleAutoPauseMusic {
|
||||
@@ -143,7 +129,7 @@ static SInt64 const kMenuItemUpdateWaitTime = 1;
|
||||
//
|
||||
// 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 (musicPlayers.selectedMusicPlayer.isRunning) {
|
||||
if (musicPlayers.selectedMusicPlayer.running) {
|
||||
menuItem.attributedTitle = nil;
|
||||
menuItem.toolTip = nil;
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMBackgroundMusicDevice.cpp
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
// Copyright © 2017 Andrew Tonner
|
||||
//
|
||||
|
||||
@@ -239,7 +239,13 @@ BGMBackgroundMusicDevice::ResponsibleBundleIDsOf(CACFString inParentBundleID)
|
||||
// Discord
|
||||
{ "com.hnc.Discord", { "com.hnc.Discord.helper" } },
|
||||
// Skype
|
||||
{ "com.skype.skype", { "com.skype.skype.Helper" } }
|
||||
{ "com.skype.skype", { "com.skype.skype.Helper" } },
|
||||
// Google Chrome
|
||||
{ "com.google.Chrome", { "com.google.Chrome.helper" } },
|
||||
// Microsoft Edge
|
||||
{ "com.microsoft.edgemac", { "com.microsoft.edgemac.helper" } },
|
||||
// Arc
|
||||
{ "company.thebrowser.Browser", { "company.thebrowser.browser.helper" } }
|
||||
};
|
||||
|
||||
// Parallels' VM "dock helper" apps have bundle IDs like
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMDebugLoggingMenuItem.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
// A menu item in the main menu that enables/disables debug logging. Only visible if you hold the
|
||||
// option down when you click the status bar icon to reveal the main menu.
|
||||
//
|
||||
// TODO: It would be better to have this menu item in the Preferences menu (maybe in an Advanced
|
||||
// section) and always visible, but first we'd need to add something that tells the user how
|
||||
// to view the log messages. Or better yet, something that automatically opens them.
|
||||
//
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@interface BGMDebugLoggingMenuItem : NSObject
|
||||
|
||||
- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem;
|
||||
|
||||
// True if the main menu is showing hidden items/options because the user held the option key when
|
||||
// they clicked the icon. This class makes the debug logging menu item visible if this property has
|
||||
// been set true or if debug logging is enabled.
|
||||
@property (nonatomic) BOOL menuShowingExtraOptions;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMDebugLoggingMenuItem.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMDebugLoggingMenuItem.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "BGMDebugLogging.h"
|
||||
#import "CADebugMacros.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@implementation BGMDebugLoggingMenuItem {
|
||||
NSMenuItem* _menuItem;
|
||||
BOOL _menuShowingExtraOptions;
|
||||
}
|
||||
|
||||
- (instancetype) initWithMenuItem:(NSMenuItem*)menuItem {
|
||||
if ((self = [super init])) {
|
||||
_menuItem = menuItem;
|
||||
_menuItem.state =
|
||||
BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
|
||||
[self setMenuShowingExtraOptions:NO];
|
||||
|
||||
// Enable/disable debug logging when the menu item is clicked.
|
||||
menuItem.target = self;
|
||||
menuItem.action = @selector(toggleDebugLogging);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) setMenuShowingExtraOptions:(BOOL)showingExtra {
|
||||
_menuShowingExtraOptions = showingExtra;
|
||||
_menuItem.hidden = !BGMDebugLoggingIsEnabled() && !showingExtra;
|
||||
|
||||
DebugMsg("BGMDebugLoggingMenuItem::menuShowingExtraOptions: %s the menu item",
|
||||
_menuItem.hidden ? "Hiding" : "Showing");
|
||||
}
|
||||
|
||||
- (void) toggleDebugLogging {
|
||||
BGMSetDebugLoggingEnabled(!BGMDebugLoggingIsEnabled());
|
||||
_menuItem.state = BGMDebugLoggingIsEnabled() ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
|
||||
DebugMsg("BGMDebugLoggingMenuItem::toggleDebugLogging: Debug logging %s",
|
||||
BGMDebugLoggingIsEnabled() ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -104,7 +104,7 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
||||
// Register a listener that will update the slider when the user changes the volume or
|
||||
// mutes/unmutes their audio.
|
||||
BGMOutputVolumeMenuItem* __weak weakSelf = self;
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [=] {
|
||||
[weakSelf updateVolumeSlider];
|
||||
});
|
||||
}
|
||||
@@ -211,36 +211,53 @@ NSString* const __nonnull kGenericOutputDeviceName = @"Output Device";
|
||||
// datasource, the device's name is set as this menu item's tooltip. Falls back to a generic name if
|
||||
// the device returns an error when queried.
|
||||
- (void) updateLabelAndToolTip {
|
||||
BOOL didSetLabel = NO;
|
||||
|
||||
try {
|
||||
if (outputDevice.HasDataSourceControl(kScope, kMasterChannel)) {
|
||||
// The device has datasources, so use the current datasource's name like macOS does.
|
||||
UInt32 dataSourceID = outputDevice.GetCurrentDataSourceID(kScope, kMasterChannel);
|
||||
|
||||
deviceLabel.stringValue =
|
||||
(__bridge_transfer NSString*)outputDevice.CopyDataSourceNameForID(kScope,
|
||||
kMasterChannel,
|
||||
dataSourceID);
|
||||
didSetLabel = YES; // So we know not to change the text if setting the tooltip fails.
|
||||
|
||||
// Set the tooltip of the menu item (the container) rather than the label because menu
|
||||
// items' tooltips will still appear when a different app is focused and, as far as I
|
||||
// know, BGMApp should never be the foreground app.
|
||||
self.toolTip = (__bridge_transfer NSString*)outputDevice.CopyName();
|
||||
} else {
|
||||
deviceLabel.stringValue = (__bridge_transfer NSString*)outputDevice.CopyName();
|
||||
self.toolTip = nil;
|
||||
}
|
||||
} catch (const CAException& e) {
|
||||
BGMLogException(e);
|
||||
|
||||
// The device returned an error, so set the label to a generic device name, since we don't
|
||||
// want to leave it set to the previous device's name.
|
||||
if (outputDevice.GetObjectID() == kAudioObjectUnknown) {
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: Output device unknown. Using the "
|
||||
"generic label.");
|
||||
self.toolTip = nil;
|
||||
deviceLabel.stringValue = kGenericOutputDeviceName;
|
||||
} else {
|
||||
BOOL didSetLabel = NO;
|
||||
|
||||
if (!didSetLabel) {
|
||||
deviceLabel.stringValue = kGenericOutputDeviceName;
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: Output device: %u",
|
||||
outputDevice.GetObjectID());
|
||||
|
||||
try {
|
||||
if (outputDevice.HasDataSourceControl(kScope, kMasterChannel)) {
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting data source ID");
|
||||
// The device has datasources, so use the current datasource's name like macOS does.
|
||||
UInt32 dataSourceID = outputDevice.GetCurrentDataSourceID(kScope, kMasterChannel);
|
||||
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: "
|
||||
"Getting name for data source %u",
|
||||
dataSourceID);
|
||||
deviceLabel.stringValue =
|
||||
(__bridge_transfer NSString*)outputDevice.CopyDataSourceNameForID(
|
||||
kScope, kMasterChannel, dataSourceID);
|
||||
|
||||
// So we know not to change the text if setting the tooltip fails.
|
||||
didSetLabel = YES;
|
||||
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting device name");
|
||||
// Set the tooltip of the menu item (the container) rather than the label because
|
||||
// menu items' tooltips will still appear when a different app is focused and, as
|
||||
// far as I know, BGMApp should never be the foreground app.
|
||||
self.toolTip = (__bridge_transfer NSString*)outputDevice.CopyName();
|
||||
} else {
|
||||
DebugMsg("BGMOutputVolumeMenuItem::updateLabelAndToolTip: Getting device name");
|
||||
deviceLabel.stringValue = (__bridge_transfer NSString*)outputDevice.CopyName();
|
||||
self.toolTip = nil;
|
||||
}
|
||||
} catch (const CAException& e) {
|
||||
BGMLogException(e);
|
||||
|
||||
// The device returned an error, so set the label to a generic device name, since we
|
||||
// don't want to leave it set to the previous device's name.
|
||||
self.toolTip = nil;
|
||||
|
||||
if (!didSetLabel) {
|
||||
deviceLabel.stringValue = kGenericOutputDeviceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+190
-124
@@ -17,7 +17,7 @@
|
||||
// BGMPlayThrough.cpp
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016, 2017, 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "BGM_Utils.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CAAtomic.h"
|
||||
#include "CAHALAudioSystemObject.h"
|
||||
#include "CAPropertyAddress.h"
|
||||
|
||||
@@ -57,9 +56,20 @@ BGMPlayThrough::BGMPlayThrough(BGMAudioDevice inInputDevice, BGMAudioDevice inOu
|
||||
|
||||
BGMPlayThrough::~BGMPlayThrough()
|
||||
{
|
||||
CAMutex::Locker stateLocker(mStateMutex);
|
||||
|
||||
BGMLogAndSwallowExceptionsMsg("BGMPlayThrough::~BGMPlayThrough", "Deactivate", [&]() {
|
||||
Deactivate();
|
||||
});
|
||||
|
||||
// If one of the IOProcs failed to stop, CoreAudio could (at least in theory) still call it
|
||||
// after this point. This isn't a solution, but calling DeallocateBuffer instead of letting it
|
||||
// deallocate itself should at least make the error less likely to cause a segfault, since
|
||||
// DeallocateBuffer takes the buffer locks and sets mBuffer to null.
|
||||
//
|
||||
// TODO: It probably wouldn't be too hard to fix this properly by giving the IOProcs weak refs
|
||||
// to the BGMPlayThrough object instead of raw pointers.
|
||||
DeallocateBuffer();
|
||||
|
||||
if(mOutputDeviceIOProcSemaphore != SEMAPHORE_NULL)
|
||||
{
|
||||
@@ -97,7 +107,7 @@ void BGMPlayThrough::Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutp
|
||||
catch (...)
|
||||
{
|
||||
// Clean up.
|
||||
mBuffer.Deallocate();
|
||||
DeallocateBuffer();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -178,7 +188,7 @@ void BGMPlayThrough::Deactivate()
|
||||
DebugMsg("BGMPlayThrough::Deactivate: Deactivating playthrough");
|
||||
|
||||
bool inputDeviceIsBGMDevice = true;
|
||||
|
||||
|
||||
CATry
|
||||
inputDeviceIsBGMDevice = mInputDevice.IsBGMDeviceInstance();
|
||||
CACatch
|
||||
@@ -231,13 +241,29 @@ void BGMPlayThrough::AllocateBuffer()
|
||||
Throw(CAException(kAudioHardwareUnsupportedOperationError));
|
||||
}
|
||||
|
||||
// Need to lock the buffer mutexes to make sure the IOProcs aren't accessing it. The order is
|
||||
// important here. We always lock them in the same order to prevent deadlocks.
|
||||
CAMutex::Locker lockerInput(mBufferInputMutex);
|
||||
CAMutex::Locker lockerOutput(mBufferOutputMutex);
|
||||
|
||||
mBuffer = std::unique_ptr<CARingBuffer>(new CARingBuffer);
|
||||
|
||||
// The calculation for the size of the buffer is from Apple's CAPlayThrough.cpp sample code
|
||||
//
|
||||
// TODO: Test playthrough with hardware with more than 2 channels per frame, a sample (virtual) format other than
|
||||
// 32-bit floats and/or an IO buffer size other than 512 frames
|
||||
mBuffer.Allocate(outputFormat[0].mChannelsPerFrame,
|
||||
outputFormat[0].mBytesPerFrame,
|
||||
mOutputDevice.GetIOBufferSize() * 20);
|
||||
mBuffer->Allocate(outputFormat[0].mChannelsPerFrame,
|
||||
outputFormat[0].mBytesPerFrame,
|
||||
mOutputDevice.GetIOBufferSize() * 20);
|
||||
}
|
||||
|
||||
void BGMPlayThrough::DeallocateBuffer()
|
||||
{
|
||||
// Need to lock the buffer mutexes to make sure the IOProcs aren't accessing it. The order is
|
||||
// important here. We always lock them in the same order to prevent deadlocks.
|
||||
CAMutex::Locker lockerInput(mBufferInputMutex);
|
||||
CAMutex::Locker lockerOutput(mBufferOutputMutex);
|
||||
mBuffer = nullptr; // Note that the buffer's destructor will deallocate it.
|
||||
}
|
||||
|
||||
void BGMPlayThrough::CreateIOProcIDs()
|
||||
@@ -569,20 +595,21 @@ OSStatus BGMPlayThrough::WaitForOutputDeviceToStart() noexcept
|
||||
while((theError != KERN_SUCCESS) && // Signalled from the IOProc.
|
||||
(state == IOState::Starting) && // IO state changed.
|
||||
(waitedNsec < kStartIOTimeoutNsec)); // Timed out.
|
||||
|
||||
#if DEBUG
|
||||
UInt64 startedBy = mach_absolute_time();
|
||||
|
||||
struct mach_timebase_info baseInfo = { 0, 0 };
|
||||
mach_timebase_info(&baseInfo);
|
||||
UInt64 base = baseInfo.numer / baseInfo.denom;
|
||||
|
||||
DebugMsg("BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f ms "
|
||||
"after entering WaitForOutputDeviceToStart.",
|
||||
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,
|
||||
static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);
|
||||
#endif
|
||||
|
||||
|
||||
if(BGMDebugLoggingIsEnabled())
|
||||
{
|
||||
UInt64 startedBy = mach_absolute_time();
|
||||
|
||||
struct mach_timebase_info baseInfo = { 0, 0 };
|
||||
mach_timebase_info(&baseInfo);
|
||||
UInt64 base = baseInfo.numer / baseInfo.denom;
|
||||
|
||||
DebugMsg("BGMPlayThrough::WaitForOutputDeviceToStart: Started %f ms after notification, %f "
|
||||
"ms after entering WaitForOutputDeviceToStart.",
|
||||
static_cast<Float64>(startedBy - mToldOutputDeviceToStartAt) * base / NSEC_PER_MSEC,
|
||||
static_cast<Float64>(startedBy - startedAt) * base / NSEC_PER_MSEC);
|
||||
}
|
||||
|
||||
// Figure out which error code to return.
|
||||
switch (theError)
|
||||
{
|
||||
@@ -604,7 +631,7 @@ OSStatus BGMPlayThrough::WaitForOutputDeviceToStart() noexcept
|
||||
|
||||
// Release any threads waiting for the output device to start. This function doesn't take mStateMutex
|
||||
// because it gets called on the IO thread, which is realtime priority.
|
||||
void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart() const
|
||||
void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart()
|
||||
{
|
||||
if(mActive)
|
||||
{
|
||||
@@ -612,13 +639,10 @@ void BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart() const
|
||||
|
||||
if(semaphore != SEMAPHORE_NULL)
|
||||
{
|
||||
DebugMsg("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart: Releasing waiting threads");
|
||||
mRTLogger.LogReleasingWaitingThreads();
|
||||
|
||||
kern_return_t theError = semaphore_signal_all(semaphore);
|
||||
|
||||
// TODO: Tell another thread to log this error, since we might be on a realtime thread.
|
||||
BGM_Utils::LogIfMachError("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart",
|
||||
"semaphore_signal_all",
|
||||
theError);
|
||||
mRTLogger.LogIfMachError_ReleaseWaitingThreadsSignal(theError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,12 +891,15 @@ void BGMPlayThrough::HandleBGMDeviceIsRunning(BGMPlayThrough* refCon)
|
||||
isRunningSomewhereOtherThanBGMApp =
|
||||
IsRunningSomewhereOtherThanBGMApp(refCon->mInputDevice);
|
||||
});
|
||||
|
||||
DebugMsg("BGMPlayThrough::HandleBGMDeviceIsRunning: "
|
||||
"BGMDevice is %srunning somewhere other than BGMApp",
|
||||
isRunningSomewhereOtherThanBGMApp ? "" : "not ");
|
||||
|
||||
if(isRunningSomewhereOtherThanBGMApp)
|
||||
{
|
||||
#if DEBUG
|
||||
refCon->mToldOutputDeviceToStartAt = mach_absolute_time();
|
||||
#endif
|
||||
|
||||
// TODO: Handle expected exceptions (mostly CAExceptions from PublicUtility classes) in Start.
|
||||
// For any that can't be handled sensibly in Start, catch them here and retry a few
|
||||
// times (with a very short delay) before handling them by showing an unobtrusive error
|
||||
@@ -906,15 +933,14 @@ void BGMPlayThrough::HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlay
|
||||
// static
|
||||
bool BGMPlayThrough::IsRunningSomewhereOtherThanBGMApp(const BGMAudioDevice& inBGMDevice)
|
||||
{
|
||||
return CFBooleanGetValue(
|
||||
static_cast<CFBooleanRef>(
|
||||
inBGMDevice.GetPropertyData_CFType(kBGMRunningSomewhereOtherThanBGMAppAddress)));
|
||||
auto type = inBGMDevice.GetPropertyData_CFType(kBGMRunningSomewhereOtherThanBGMAppAddress);
|
||||
return type && CFBooleanGetValue(static_cast<CFBooleanRef>(type));
|
||||
}
|
||||
|
||||
#pragma mark IOProcs
|
||||
|
||||
// Note that the IOProcs will very likely not run on the same thread and that they intentionally don't
|
||||
// lock any mutexes.
|
||||
// Note that the IOProcs will very likely not run on the same thread and that they intentionally
|
||||
// only lock mutexes around their use of mBuffer.
|
||||
|
||||
// static
|
||||
OSStatus BGMPlayThrough::InputDeviceIOProc(AudioObjectID inDevice,
|
||||
@@ -932,6 +958,7 @@ OSStatus BGMPlayThrough::InputDeviceIOProc(AudioObjectID inDevice,
|
||||
|
||||
IOState state;
|
||||
UpdateIOProcState("InputDeviceIOProc",
|
||||
refCon->mRTLogger,
|
||||
refCon->mInputDeviceIOProcState,
|
||||
refCon->mInputDeviceIOProcID,
|
||||
refCon->mInputDevice,
|
||||
@@ -952,16 +979,30 @@ OSStatus BGMPlayThrough::InputDeviceIOProc(AudioObjectID inDevice,
|
||||
|
||||
UInt32 framesToStore = inInputData->mBuffers[0].mDataByteSize / (SizeOf32(Float32) * 2);
|
||||
|
||||
CARingBufferError err =
|
||||
refCon->mBuffer.Store(inInputData,
|
||||
framesToStore,
|
||||
static_cast<CARingBuffer::SampleTime>(inInputTime->mSampleTime));
|
||||
|
||||
HandleRingBufferError(err, "InputDeviceIOProc", "mBuffer.Store");
|
||||
|
||||
CAMemoryBarrier();
|
||||
refCon->mLastInputSampleTime = inInputTime->mSampleTime;
|
||||
|
||||
// See the comments in OutputDeviceIOProc where it locks mBufferOutputMutex.
|
||||
CAMutex::Tryer tryer(refCon->mBufferInputMutex);
|
||||
|
||||
// Disable a warning about accessing mBuffer without holding both mBufferInputMutex and
|
||||
// mBufferOutputMutex. Explained further in OutputDeviceIOProc.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wthread-safety"
|
||||
if(tryer.HasLock() && refCon->mBuffer)
|
||||
{
|
||||
CARingBufferError err =
|
||||
refCon->mBuffer->Store(inInputData,
|
||||
framesToStore,
|
||||
static_cast<CARingBuffer::SampleTime>(
|
||||
inInputTime->mSampleTime));
|
||||
#pragma clang diagnostic pop
|
||||
refCon->mRTLogger.LogIfRingBufferError_Store(err);
|
||||
|
||||
refCon->mLastInputSampleTime = inInputTime->mSampleTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
refCon->mRTLogger.LogRingBufferUnavailable("InputDeviceIOProc", tryer.HasLock());
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -974,13 +1015,14 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
||||
const AudioTimeStamp* inOutputTime,
|
||||
void* __nullable inClientData)
|
||||
{
|
||||
#pragma unused (inDevice, inNow, inInputData, inInputTime, inOutputTime)
|
||||
#pragma unused (inDevice, inNow, inInputData, inInputTime)
|
||||
|
||||
// refCon (reference context) is the instance that created the IOProc
|
||||
BGMPlayThrough* const refCon = static_cast<BGMPlayThrough*>(inClientData);
|
||||
|
||||
IOState state;
|
||||
const bool didChangeState = UpdateIOProcState("OutputDeviceIOProc",
|
||||
refCon->mRTLogger,
|
||||
refCon->mOutputDeviceIOProcState,
|
||||
refCon->mOutputDeviceIOProcID,
|
||||
refCon->mOutputDevice,
|
||||
@@ -989,6 +1031,7 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
||||
if(state == IOState::Stopped || state == IOState::Stopping)
|
||||
{
|
||||
// Return early, since we just asked to stop. (Or something really weird is going on.)
|
||||
FillWithSilence(outOutputData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -1008,8 +1051,7 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
||||
if(refCon->mLastInputSampleTime == -1)
|
||||
{
|
||||
// Return early, since we don't have any data to output yet.
|
||||
//
|
||||
// TODO: Write silence to outOutputData here
|
||||
FillWithSilence(outOutputData);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -1020,13 +1062,8 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
||||
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - refCon->mLastInputSampleTime;
|
||||
|
||||
// Log if we dropped frames
|
||||
if(refCon->mFirstInputSampleTime != refCon->mLastInputSampleTime)
|
||||
{
|
||||
DebugMsg("BGMPlayThrough::OutputDeviceIOProc: Dropped %f frames before output started. %s%f %s%f",
|
||||
(refCon->mLastInputSampleTime - refCon->mFirstInputSampleTime),
|
||||
"mFirstInputSampleTime=", refCon->mFirstInputSampleTime,
|
||||
"mLastInputSampleTime=", refCon->mLastInputSampleTime);
|
||||
}
|
||||
refCon->mRTLogger.LogIfDroppedFrames(refCon->mFirstInputSampleTime,
|
||||
refCon->mLastInputSampleTime);
|
||||
}
|
||||
|
||||
CARingBuffer::SampleTime readHeadSampleTime =
|
||||
@@ -1035,47 +1072,93 @@ OSStatus BGMPlayThrough::OutputDeviceIOProc(AudioObjectID inDevice,
|
||||
static_cast<CARingBuffer::SampleTime>(refCon->mLastInputSampleTime);
|
||||
|
||||
UInt32 framesToOutput = outOutputData->mBuffers[0].mDataByteSize / (SizeOf32(Float32) * 2);
|
||||
|
||||
// Very occasionally (at least for me) our read head gets ahead of input, i.e. we haven't received any new input since
|
||||
// this IOProc was last called, and we have to recalculate its position. I figure this might be caused by clock drift
|
||||
// but I'm really not sure. It also happens if the input or output sample times are restarted from zero.
|
||||
//
|
||||
// We also recalculate the offset if the read head is outside of the ring buffer. This happens for example when you plug
|
||||
// in or unplug headphones, which causes the output sample times to be restarted from zero.
|
||||
//
|
||||
// The vast majority of the time, just using lastInputSampleTime as the read head time instead of the one we calculate
|
||||
// would work fine (and would also account for the above).
|
||||
SInt64 bufferStartTime, bufferEndTime;
|
||||
CARingBufferError err = refCon->mBuffer.GetTimeBounds(bufferStartTime, bufferEndTime);
|
||||
bool outOfBounds = false;
|
||||
if(err == kCARingBufferError_OK)
|
||||
{
|
||||
outOfBounds = (readHeadSampleTime < bufferStartTime) || (readHeadSampleTime - framesToOutput > bufferEndTime);
|
||||
}
|
||||
if(lastInputSampleTime < readHeadSampleTime || outOfBounds)
|
||||
{
|
||||
DebugMsg("BGMPlayThrough::OutputDeviceIOProc: No input samples ready at output sample time. %s%lld %s%lld %s%f",
|
||||
"lastInputSampleTime=", lastInputSampleTime,
|
||||
"readHeadSampleTime=", readHeadSampleTime,
|
||||
"mInToOutSampleOffset=", refCon->mInToOutSampleOffset);
|
||||
|
||||
// Recalculate the in-to-out offset and read head
|
||||
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - lastInputSampleTime;
|
||||
readHeadSampleTime = static_cast<CARingBuffer::SampleTime>(inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);
|
||||
}
|
||||
|
||||
// Copy the frames from the ring buffer
|
||||
err = refCon->mBuffer.Fetch(outOutputData, framesToOutput, readHeadSampleTime);
|
||||
|
||||
HandleRingBufferError(err, "OutputDeviceIOProc", "mBuffer.Fetch");
|
||||
|
||||
// When the input and output devices are set, during start up or because the user changed the
|
||||
// output device, this class (re)allocates the ring buffer (mBuffer). We try to take this
|
||||
// lock before accessing the buffer to make sure it's allocated.
|
||||
//
|
||||
// If we don't get the lock, another thread must be allocating or deallocating it, so we just
|
||||
// give up. We can't avoid audio glitches while changing devices anyway. This class tries to
|
||||
// make sure the IOProcs aren't running when it allocates the buffer, but it can't guarantee
|
||||
// that.
|
||||
//
|
||||
// Note that this is only realtime safe because we only try to lock the mutex. If another
|
||||
// thread has the mutex, it will be a non-realtime thread, so we can't wait for it.
|
||||
CAMutex::Tryer tryer(refCon->mBufferOutputMutex);
|
||||
|
||||
// Disable a warning about accessing mBuffer without holding both mBufferInputMutex and
|
||||
// mBufferOutputMutex. The input IOProc always writes ahead of where the output IOProc will read
|
||||
// in a given IO cycle, so it's safe for them to read and write at the same time.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wthread-safety"
|
||||
if(tryer.HasLock() && refCon->mBuffer)
|
||||
{
|
||||
// Very occasionally (at least for me) our read head gets ahead of input, i.e. we haven't
|
||||
// received any new input since this IOProc was last called, and we have to recalculate its
|
||||
// position. I figure this might be caused by clock drift but I'm really not sure. It also
|
||||
// happens if the input or output sample times are restarted from zero.
|
||||
//
|
||||
// We also recalculate the offset if the read head is outside of the ring buffer. This
|
||||
// happens for example when you plug in or unplug headphones, which causes the output sample
|
||||
// times to be restarted from zero.
|
||||
//
|
||||
// The vast majority of the time, just using lastInputSampleTime as the read head time
|
||||
// instead of the one we calculate would work fine (and would also account for the above).
|
||||
SInt64 bufferStartTime, bufferEndTime;
|
||||
CARingBufferError err = refCon->mBuffer->GetTimeBounds(bufferStartTime, bufferEndTime);
|
||||
bool outOfBounds = false;
|
||||
|
||||
if(err == kCARingBufferError_OK)
|
||||
{
|
||||
outOfBounds = (readHeadSampleTime < bufferStartTime)
|
||||
|| (readHeadSampleTime - framesToOutput > bufferEndTime);
|
||||
}
|
||||
|
||||
if(lastInputSampleTime < readHeadSampleTime || outOfBounds)
|
||||
{
|
||||
refCon->mRTLogger.LogNoSamplesReady(lastInputSampleTime,
|
||||
readHeadSampleTime,
|
||||
refCon->mInToOutSampleOffset);
|
||||
|
||||
// Recalculate the in-to-out offset and read head.
|
||||
refCon->mInToOutSampleOffset = inOutputTime->mSampleTime - lastInputSampleTime;
|
||||
readHeadSampleTime = static_cast<CARingBuffer::SampleTime>(
|
||||
inOutputTime->mSampleTime - refCon->mInToOutSampleOffset);
|
||||
}
|
||||
|
||||
// Copy the frames from the ring buffer.
|
||||
err = refCon->mBuffer->Fetch(outOutputData, framesToOutput, readHeadSampleTime);
|
||||
refCon->mRTLogger.LogIfRingBufferError_Fetch(err);
|
||||
|
||||
if(err != kCARingBufferError_OK)
|
||||
{
|
||||
FillWithSilence(outOutputData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
refCon->mRTLogger.LogRingBufferUnavailable("OutputDeviceIOProc", tryer.HasLock());
|
||||
FillWithSilence(outOutputData);
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
refCon->mLastOutputSampleTime = inOutputTime->mSampleTime;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
// static
|
||||
bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
inline void BGMPlayThrough::FillWithSilence(AudioBufferList* ioBuffer)
|
||||
{
|
||||
for(UInt32 i = 0; i < ioBuffer->mNumberBuffers; i++)
|
||||
{
|
||||
memset(ioBuffer->mBuffers[i].mData, 0, ioBuffer->mBuffers[i].mDataByteSize);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool BGMPlayThrough::UpdateIOProcState(const char* inCallerName,
|
||||
BGMPlayThroughRTLogger& inRTLogger,
|
||||
std::atomic<IOState>& inState,
|
||||
AudioDeviceIOProcID __nullable inIOProcID,
|
||||
BGMAudioDevice& inDevice,
|
||||
@@ -1088,6 +1171,9 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
//
|
||||
// compare_exchange_strong will return true iff it changed inState from Starting to Running.
|
||||
// Otherwise it will set prevState to the current value of inState.
|
||||
//
|
||||
// TODO: We probably don't actually need memory_order_seq_cst (the default). Would it be worth
|
||||
// changing? Might be worth checking for the other atomics/barriers in this class, too.
|
||||
IOState prevState = IOState::Starting;
|
||||
bool didChangeState = inState.compare_exchange_strong(prevState, IOState::Running);
|
||||
|
||||
@@ -1105,21 +1191,28 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
{
|
||||
// The IOProc isn't Starting or Running, so it must be Stopping. That is, it's been
|
||||
// told to stop itself.
|
||||
|
||||
BGMAssert(outNewState == IOState::Stopping,
|
||||
"BGMPlayThrough::UpdateIOProcState: Unexpected state: %d",
|
||||
outNewState);
|
||||
|
||||
bool stoppedSuccessfully = false;
|
||||
BGMLogAndSwallowExceptionsMsg("BGMPlayThrough::UpdateIOProcState", callerName, [&]() {
|
||||
// TODO: If this throws, tell another thread to log the exception rather than
|
||||
// logging it from a real-time thread.
|
||||
|
||||
try
|
||||
{
|
||||
inDevice.StopIOProc(inIOProcID);
|
||||
|
||||
// StopIOProc didn't throw, so the IOProc won't be called again until the next
|
||||
// time playthrough is started.
|
||||
stoppedSuccessfully = true;
|
||||
});
|
||||
}
|
||||
catch(CAException e)
|
||||
{
|
||||
inRTLogger.LogExceptionStoppingIOProc(inCallerName, e.GetError());
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
inRTLogger.LogExceptionStoppingIOProc(inCallerName);
|
||||
}
|
||||
|
||||
if(stoppedSuccessfully)
|
||||
{
|
||||
@@ -1132,7 +1225,7 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
//
|
||||
// Stop won't return until the IOProc has changed inState to Stopped, unless it
|
||||
// times out, so Stop should still be waiting. And since Start and Stop are
|
||||
// mutually exclusive, so this should be safe.
|
||||
// mutually exclusive, this should be safe.
|
||||
//
|
||||
// But if Stop has timed out and inState has changed, we leave it in its new
|
||||
// state (unless there's some ABA problem thing happening), which I suspect is
|
||||
@@ -1145,8 +1238,8 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugMsg("BGMPlayThrough::UpdateIOProcState: inState changed since last read "
|
||||
"outNewState = %d", outNewState);
|
||||
inRTLogger.LogUnexpectedIOStateAfterStopping(inCallerName,
|
||||
static_cast<int>(outNewState));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1155,30 +1248,3 @@ bool BGMPlayThrough::UpdateIOProcState(const char* __nullable callerName,
|
||||
return didChangeState;
|
||||
}
|
||||
|
||||
// static
|
||||
void BGMPlayThrough::HandleRingBufferError(CARingBufferError inErr,
|
||||
const char* inMethodName,
|
||||
const char* inCallReturningErr)
|
||||
{
|
||||
#if DEBUG
|
||||
if(inErr != kCARingBufferError_OK)
|
||||
{
|
||||
const char* errStr = (inErr == kCARingBufferError_TooMuch ? "kCARingBufferError_TooMuch" :
|
||||
(inErr == kCARingBufferError_CPUOverload ? "kCARingBufferError_CPUOverload" : "unknown error"));
|
||||
|
||||
DebugMsg("BGMPlayThrough::%s: %s returned %s (%d)", inMethodName, inCallReturningErr, errStr, inErr);
|
||||
|
||||
// kCARingBufferError_CPUOverload wouldn't mean we have a bug, but I think kCARingBufferError_TooMuch would
|
||||
if(inErr != kCARingBufferError_CPUOverload)
|
||||
{
|
||||
Throw(CAException(inErr));
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Not sure what we should do to handle these errors in release builds, if anything.
|
||||
// TODO: There's code in Apple's CAPlayThrough.cpp sample code that handles them. (Look for "kCARingBufferError_OK"
|
||||
// around line 707.) Should be easy enough to use, but it's more complicated that just directly copying it.
|
||||
#pragma unused (inErr, inMethodName, inCallReturningErr)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMPlayThrough.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
// Copyright © 2016, 2017, 2020 Kyle Neideck
|
||||
//
|
||||
// Reads audio from an input device and immediately writes it to an output device. We currently use this class with the input
|
||||
// device always set to BGMDevice and the output device set to the one selected in the preferences menu.
|
||||
@@ -41,14 +41,17 @@
|
||||
|
||||
// Local Includes
|
||||
#include "BGMAudioDevice.h"
|
||||
#include "BGMPlayThroughRTLogger.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CARingBuffer.h"
|
||||
#include "CAMutex.h"
|
||||
#include "CARingBuffer.h"
|
||||
#include "BGMThreadSafetyAnalysis.h"
|
||||
|
||||
// STL Includes
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
// System Includes
|
||||
#include <mach/semaphore.h>
|
||||
@@ -78,7 +81,8 @@ public:
|
||||
|
||||
private:
|
||||
/*! @throws CAException */
|
||||
void Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice);
|
||||
void Init(BGMAudioDevice inInputDevice, BGMAudioDevice inOutputDevice)
|
||||
REQUIRES(mStateMutex);
|
||||
|
||||
public:
|
||||
/*! @throws CAException */
|
||||
@@ -87,7 +91,8 @@ public:
|
||||
void Deactivate();
|
||||
|
||||
private:
|
||||
void AllocateBuffer();
|
||||
void AllocateBuffer() REQUIRES(mStateMutex);
|
||||
void DeallocateBuffer();
|
||||
|
||||
/*! @throws CAException */
|
||||
void CreateIOProcIDs();
|
||||
@@ -97,7 +102,7 @@ private:
|
||||
@return True if both IOProcs are stopped.
|
||||
@nonthreadsafe
|
||||
*/
|
||||
bool CheckIOProcsAreStopped() const noexcept; // TODO: REQUIRES(mStateMutex);
|
||||
bool CheckIOProcsAreStopped() const noexcept REQUIRES(mStateMutex);
|
||||
|
||||
public:
|
||||
/*!
|
||||
@@ -115,7 +120,8 @@ public:
|
||||
OSStatus WaitForOutputDeviceToStart() noexcept;
|
||||
|
||||
private:
|
||||
void ReleaseThreadsWaitingForOutputToStart() const;
|
||||
/*! Real-time safe. */
|
||||
void ReleaseThreadsWaitingForOutputToStart();
|
||||
|
||||
public:
|
||||
OSStatus Stop();
|
||||
@@ -146,7 +152,10 @@ private:
|
||||
AudioBufferList* outOutputData,
|
||||
const AudioTimeStamp* inOutputTime,
|
||||
void* __nullable inClientData);
|
||||
|
||||
|
||||
/*! Fills the given ABL with zeroes to make it silent. */
|
||||
static inline void FillWithSilence(AudioBufferList* ioBuffer);
|
||||
|
||||
// The state of an IOProc. Used by the IOProc to tell other threads when it's finished starting. Used by other
|
||||
// threads to tell the IOProc to stop itself. (Probably used for other things as well.)
|
||||
enum class IOState
|
||||
@@ -156,27 +165,45 @@ private:
|
||||
|
||||
// The IOProcs call this to update their IOState member. Also stops the IOProc if its state has been set to Stopping.
|
||||
// Returns true if it changes the state.
|
||||
static bool UpdateIOProcState(const char* __nullable callerName,
|
||||
static bool UpdateIOProcState(const char* inCallerName,
|
||||
BGMPlayThroughRTLogger& inRTLogger,
|
||||
std::atomic<IOState>& inState,
|
||||
AudioDeviceIOProcID __nullable inIOProcID,
|
||||
BGMAudioDevice& inDevice,
|
||||
IOState& outNewState);
|
||||
|
||||
static void HandleRingBufferError(CARingBufferError err,
|
||||
const char* methodName,
|
||||
const char* callReturningErr);
|
||||
|
||||
private:
|
||||
CARingBuffer mBuffer;
|
||||
std::unique_ptr<CARingBuffer> mBuffer PT_GUARDED_BY(mBufferInputMutex)
|
||||
PT_GUARDED_BY(mBufferOutputMutex) { nullptr };
|
||||
|
||||
AudioDeviceIOProcID __nullable mInputDeviceIOProcID { nullptr };
|
||||
AudioDeviceIOProcID __nullable mOutputDeviceIOProcID { nullptr };
|
||||
|
||||
BGMAudioDevice mInputDevice { kAudioObjectUnknown };
|
||||
BGMAudioDevice mOutputDevice { kAudioObjectUnknown };
|
||||
|
||||
CAMutex mStateMutex { "Playthrough state" };
|
||||
|
||||
|
||||
// mStateMutex is the general purpose mutex. mBufferInputMutex and mBufferOutputMutex are
|
||||
// just used to make sure mBuffer, the ring buffer, is allocated when the IOProcs access it. See
|
||||
// the comments in the IOProcs for details.
|
||||
//
|
||||
// If a thread might lock more than one of these mutexes, it *must* take them in this order:
|
||||
// 1. mStateMutex
|
||||
// 2. mBufferInputMutex
|
||||
// 3. mBufferOutputMutex
|
||||
//
|
||||
// The ACQUIRED_BEFORE annotations don't do anything yet. From clang's docs: "ACQUIRED_BEFORE(…)
|
||||
// and ACQUIRED_AFTER(…) are currently unimplemented. To be fixed in a future update." After
|
||||
// they've fixed that, the compiler will enforce the ordering statically.
|
||||
//
|
||||
// TODO: We can't use std::shared_lock because we're still on C++11, but we could use std::lock
|
||||
// to help ensure the locks are always taken in the right order.
|
||||
// TODO: It would be better to have a separate class for the buffer and its mutexes.
|
||||
CAMutex mStateMutex ACQUIRED_BEFORE(mBufferInputMutex)
|
||||
ACQUIRED_BEFORE(mBufferOutputMutex) { "Playthrough state" };
|
||||
CAMutex mBufferInputMutex ACQUIRED_BEFORE(mBufferOutputMutex)
|
||||
{ "Playthrough ring buffer input" };
|
||||
CAMutex mBufferOutputMutex { "Playthrough ring buffer output" };
|
||||
|
||||
// Signalled when the output IOProc runs. We use it to tell BGMDriver when the output device is ready to receive audio data.
|
||||
semaphore_t mOutputDeviceIOProcSemaphore { SEMAPHORE_NULL };
|
||||
|
||||
@@ -200,7 +227,9 @@ private:
|
||||
|
||||
// Subtract this from the output time to get the input time.
|
||||
Float64 mInToOutSampleOffset { 0.0 };
|
||||
|
||||
|
||||
BGMPlayThroughRTLogger mRTLogger;
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,521 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMPlayThroughRTLogger.cpp
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGMPlayThroughRTLogger.h"
|
||||
|
||||
// Local Includes
|
||||
#include "BGM_Utils.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CADebugMacros.h"
|
||||
|
||||
// STL Includes
|
||||
#include <atomic>
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/task.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
// Track the number of messages logged when built for the unit tests.
|
||||
#if BGM_UnitTest
|
||||
#define LogSync_Debug(inFormat, ...) do { \
|
||||
mNumDebugMessagesLogged++; \
|
||||
DebugMsg(inFormat, ## __VA_ARGS__); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LogSync_Debug(inFormat, ...) DebugMsg(inFormat, ## __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
BGMPlayThroughRTLogger::BGMPlayThroughRTLogger()
|
||||
{
|
||||
// Create the semaphore we use to wake up the logging thread when it has messages to log.
|
||||
mWakeUpLoggingThreadSemaphore = CreateSemaphore();
|
||||
|
||||
// Create the logging thread last because it starts immediately and expects the other member
|
||||
// variables to be initialised.
|
||||
mLoggingThread = std::thread(&BGMPlayThroughRTLogger::LoggingThreadEntry, this);
|
||||
}
|
||||
|
||||
// static
|
||||
semaphore_t BGMPlayThroughRTLogger::CreateSemaphore()
|
||||
{
|
||||
// TODO: Make a BGMMachSemaphore class to reduce some of this repetitive semaphore code.
|
||||
|
||||
// Create the semaphore.
|
||||
semaphore_t semaphore;
|
||||
kern_return_t error =
|
||||
semaphore_create(mach_task_self(), &semaphore, SYNC_POLICY_FIFO, 0);
|
||||
|
||||
// Check the error code.
|
||||
BGM_Utils::ThrowIfMachError("BGMPlayThroughRTLogger::CreateSemaphore",
|
||||
"semaphore_create",
|
||||
error);
|
||||
ThrowIf(semaphore == SEMAPHORE_NULL,
|
||||
CAException(kAudioHardwareUnspecifiedError),
|
||||
"BGMPlayThroughRTLogger::CreateSemaphore: Failed to create semaphore");
|
||||
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger()
|
||||
{
|
||||
// Stop the logging thread.
|
||||
mLoggingThreadShouldExit = true;
|
||||
kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);
|
||||
|
||||
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger",
|
||||
"semaphore_signal",
|
||||
error);
|
||||
|
||||
if(error == KERN_SUCCESS)
|
||||
{
|
||||
// Wait for it to stop.
|
||||
mLoggingThread.join();
|
||||
|
||||
// Destroy the semaphore.
|
||||
error = semaphore_destroy(mach_task_self(), mWakeUpLoggingThreadSemaphore);
|
||||
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::~BGMPlayThroughRTLogger",
|
||||
"semaphore_destroy",
|
||||
error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we couldn't tell it to wake up, it's not safe to wait for it to stop or to destroy the
|
||||
// semaphore. We have to detach it so its destructor doesn't cause a crash.
|
||||
mLoggingThread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Log Messages
|
||||
|
||||
void BGMPlayThroughRTLogger::LogReleasingWaitingThreads()
|
||||
{
|
||||
if(!BGMDebugLoggingIsEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mLogReleasingWaitingThreadsMsg.is_lock_free())
|
||||
{
|
||||
// Modifying mLogReleasingWaitingThreadsMsg might cause the thread to lock a mutex that
|
||||
// isn't safe to lock on a realtime thread, so just give up.
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the flag that tells the logging thread to log the message.
|
||||
mLogReleasingWaitingThreadsMsg = true;
|
||||
|
||||
// Wake the logging thread so it can log the message.
|
||||
WakeLoggingThread();
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError)
|
||||
{
|
||||
if(inError == KERN_SUCCESS)
|
||||
{
|
||||
// No error.
|
||||
return;
|
||||
}
|
||||
|
||||
if(!mReleaseWaitingThreadsSignalError.is_lock_free())
|
||||
{
|
||||
// Modifying mReleaseWaitingThreadsSignalError might cause the thread to lock a mutex that
|
||||
// isn't safe to lock on a realtime thread, so just give up.
|
||||
return;
|
||||
}
|
||||
|
||||
mReleaseWaitingThreadsSignalError = inError;
|
||||
WakeLoggingThread();
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogIfDroppedFrames(Float64 inFirstInputSampleTime,
|
||||
Float64 inLastInputSampleTime)
|
||||
{
|
||||
if(inFirstInputSampleTime == inLastInputSampleTime || !BGMDebugLoggingIsEnabled())
|
||||
{
|
||||
// Either we didn't drop any initial frames or we don't need to log a message about it.
|
||||
return;
|
||||
}
|
||||
|
||||
LogAsync(mDroppedFrames, [&]()
|
||||
{
|
||||
// Store the data to include in the log message.
|
||||
mDroppedFrames.firstInputSampleTime = inFirstInputSampleTime;
|
||||
mDroppedFrames.lastInputSampleTime = inLastInputSampleTime;
|
||||
});
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,
|
||||
CARingBuffer::SampleTime inReadHeadSampleTime,
|
||||
Float64 inInToOutSampleOffset)
|
||||
{
|
||||
if(!BGMDebugLoggingIsEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogAsync(mNoSamplesReady, [&]()
|
||||
{
|
||||
// Store the data to include in the log message.
|
||||
mNoSamplesReady.lastInputSampleTime = inLastInputSampleTime;
|
||||
mNoSamplesReady.readHeadSampleTime = inReadHeadSampleTime;
|
||||
mNoSamplesReady.inToOutSampleOffset = inInToOutSampleOffset;
|
||||
});
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogExceptionStoppingIOProc(const char* inCallerName,
|
||||
OSStatus inError,
|
||||
bool inErrorKnown)
|
||||
{
|
||||
LogAsync(mExceptionStoppingIOProc, [&]()
|
||||
{
|
||||
// Store the data to include in the log message.
|
||||
mExceptionStoppingIOProc.callerName = inCallerName;
|
||||
mExceptionStoppingIOProc.error = inError;
|
||||
mExceptionStoppingIOProc.errorKnown = inErrorKnown;
|
||||
});
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogUnexpectedIOStateAfterStopping(const char* inCallerName,
|
||||
int inIOState)
|
||||
{
|
||||
LogAsync(mUnexpectedIOStateAfterStopping, [&]()
|
||||
{
|
||||
// Store the data to include in the log message.
|
||||
mUnexpectedIOStateAfterStopping.callerName = inCallerName;
|
||||
mUnexpectedIOStateAfterStopping.ioState = inIOState;
|
||||
});
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogRingBufferUnavailable(const char* inCallerName, bool inGotLock)
|
||||
{
|
||||
LogAsync(mRingBufferUnavailable, [&]()
|
||||
{
|
||||
// Store the data to include in the log message.
|
||||
mRingBufferUnavailable.callerName = inCallerName;
|
||||
mRingBufferUnavailable.gotLock = inGotLock;
|
||||
});
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogIfRingBufferError(CARingBufferError inError,
|
||||
std::atomic<CARingBufferError>& outError)
|
||||
{
|
||||
if(inError == kCARingBufferError_OK)
|
||||
{
|
||||
// No error.
|
||||
return;
|
||||
}
|
||||
|
||||
if(!outError.is_lock_free())
|
||||
{
|
||||
// Modifying outError might cause the thread to lock a mutex that isn't safe to lock on
|
||||
// a realtime thread, so just give up.
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the error.
|
||||
outError = inError;
|
||||
|
||||
// Wake the logging thread so it can log the error.
|
||||
WakeLoggingThread();
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
void BGMPlayThroughRTLogger::LogAsync(T& inMessageData, F&& inStoreMessageData)
|
||||
{
|
||||
if(!inMessageData.shouldLogMessage.is_lock_free())
|
||||
{
|
||||
// Modifying shouldLogMessage might cause the thread to lock a mutex that isn't safe to
|
||||
// lock on a realtime thread, so just give up.
|
||||
return;
|
||||
}
|
||||
|
||||
if(inMessageData.shouldLogMessage)
|
||||
{
|
||||
// The logging thread could be reading inMessageData.
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the data to include in the log message.
|
||||
//
|
||||
// std::forward lets the compiler treat inStoreMessageData as an rvalue if the caller gave it as
|
||||
// an rvalue. No idea if that actually does anything.
|
||||
std::forward<F>(inStoreMessageData)();
|
||||
|
||||
// shouldLogMessage is a std::atomic, so this store also makes sure that the non-atomic stores
|
||||
// in inStoreMessageData will be visible to the logger thread (since the default memory order is
|
||||
// memory_order_seq_cst).
|
||||
inMessageData.shouldLogMessage = true;
|
||||
|
||||
WakeLoggingThread();
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_Warning(const char* inFormat, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, inFormat);
|
||||
|
||||
#if BGM_UnitTest
|
||||
mNumWarningMessagesLogged++;
|
||||
#endif
|
||||
|
||||
vLogWarning(inFormat, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_Error(const char* inFormat, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, inFormat);
|
||||
|
||||
#if BGM_UnitTest
|
||||
mNumErrorMessagesLogged++;
|
||||
|
||||
if(!mContinueOnErrorLogged)
|
||||
{
|
||||
vLogError(inFormat, args);
|
||||
}
|
||||
#else
|
||||
vLogError(inFormat, args);
|
||||
#endif
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#pragma mark Logging Thread
|
||||
|
||||
void BGMPlayThroughRTLogger::WakeLoggingThread()
|
||||
{
|
||||
kern_return_t error = semaphore_signal(mWakeUpLoggingThreadSemaphore);
|
||||
|
||||
BGMAssert(error == KERN_SUCCESS, "semaphore_signal (%d)", error);
|
||||
|
||||
// We can't do anything useful with the error in release builds. At least, not easily.
|
||||
(void)error;
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogMessages()
|
||||
{
|
||||
// Log the messages/errors from the realtime threads (if any).
|
||||
LogSync_ReleasingWaitingThreads();
|
||||
LogSync_ReleaseWaitingThreadsSignalError();
|
||||
LogSync_DroppedFrames();
|
||||
LogSync_NoSamplesReady();
|
||||
LogSync_ExceptionStoppingIOProc();
|
||||
LogSync_UnexpectedIOStateAfterStopping();
|
||||
LogSync_RingBufferUnavailable();
|
||||
LogSync_RingBufferError(mRingBufferStoreError, "InputDeviceIOProc");
|
||||
LogSync_RingBufferError(mRingBufferFetchError, "OutputDeviceIOProc");
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_ReleasingWaitingThreads()
|
||||
{
|
||||
if(mLogReleasingWaitingThreadsMsg)
|
||||
{
|
||||
LogSync_Debug("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart: "
|
||||
"Releasing waiting threads");
|
||||
// Reset it.
|
||||
mLogReleasingWaitingThreadsMsg = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_ReleaseWaitingThreadsSignalError()
|
||||
{
|
||||
if(mReleaseWaitingThreadsSignalError != KERN_SUCCESS)
|
||||
{
|
||||
BGM_Utils::LogIfMachError("BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart",
|
||||
"semaphore_signal_all",
|
||||
mReleaseWaitingThreadsSignalError);
|
||||
// Reset it.
|
||||
mReleaseWaitingThreadsSignalError = KERN_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_DroppedFrames()
|
||||
{
|
||||
if(mDroppedFrames.shouldLogMessage)
|
||||
{
|
||||
LogSync_Debug("BGMPlayThrough::OutputDeviceIOProc: "
|
||||
"Dropped %f frames before output started. %s%f %s%f",
|
||||
(mDroppedFrames.lastInputSampleTime - mDroppedFrames.firstInputSampleTime),
|
||||
"mFirstInputSampleTime=",
|
||||
mDroppedFrames.firstInputSampleTime,
|
||||
"mLastInputSampleTime=",
|
||||
mDroppedFrames.lastInputSampleTime);
|
||||
mDroppedFrames.shouldLogMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_NoSamplesReady()
|
||||
{
|
||||
if(mNoSamplesReady.shouldLogMessage)
|
||||
{
|
||||
LogSync_Debug("BGMPlayThrough::OutputDeviceIOProc: "
|
||||
"No input samples ready at output sample time. %s%lld %s%lld %s%f",
|
||||
"lastInputSampleTime=", mNoSamplesReady.lastInputSampleTime,
|
||||
"readHeadSampleTime=", mNoSamplesReady.readHeadSampleTime,
|
||||
"mInToOutSampleOffset=", mNoSamplesReady.inToOutSampleOffset);
|
||||
mNoSamplesReady.shouldLogMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_ExceptionStoppingIOProc()
|
||||
{
|
||||
if(mExceptionStoppingIOProc.shouldLogMessage)
|
||||
{
|
||||
const char error4CC[5] = CA4CCToCString(mExceptionStoppingIOProc.error);
|
||||
LogSync_Error("BGMPlayThrough::UpdateIOProcState: "
|
||||
"Exception while stopping IOProc %s: %s (%d)",
|
||||
mExceptionStoppingIOProc.callerName,
|
||||
mExceptionStoppingIOProc.errorKnown ? error4CC : "unknown",
|
||||
mExceptionStoppingIOProc.error);
|
||||
mExceptionStoppingIOProc.shouldLogMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_UnexpectedIOStateAfterStopping()
|
||||
{
|
||||
if(mUnexpectedIOStateAfterStopping.shouldLogMessage)
|
||||
{
|
||||
LogSync_Warning("BGMPlayThrough::UpdateIOProcState: "
|
||||
"%s IO state changed since last read. state = %d",
|
||||
mUnexpectedIOStateAfterStopping.callerName,
|
||||
mUnexpectedIOStateAfterStopping.ioState);
|
||||
mUnexpectedIOStateAfterStopping.shouldLogMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_RingBufferUnavailable()
|
||||
{
|
||||
if(mRingBufferUnavailable.shouldLogMessage)
|
||||
{
|
||||
LogSync_Warning("BGMPlayThrough::%s: Ring buffer unavailable. %s",
|
||||
mRingBufferUnavailable.callerName,
|
||||
mRingBufferUnavailable.gotLock ?
|
||||
"No buffer currently allocated." :
|
||||
"Buffer locked for allocation/deallocation by another thread.");
|
||||
mRingBufferUnavailable.shouldLogMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BGMPlayThroughRTLogger::LogSync_RingBufferError(
|
||||
std::atomic<CARingBufferError>& ioRingBufferError,
|
||||
const char* inMethodName)
|
||||
{
|
||||
CARingBufferError error = ioRingBufferError;
|
||||
|
||||
switch(error)
|
||||
{
|
||||
case kCARingBufferError_OK:
|
||||
// No error.
|
||||
return;
|
||||
case kCARingBufferError_CPUOverload:
|
||||
// kCARingBufferError_CPUOverload might not be our fault, so just log a warning.
|
||||
LogSync_Warning("BGMPlayThrough::%s: Ring buffer error: "
|
||||
"kCARingBufferError_CPUOverload (%d)",
|
||||
inMethodName,
|
||||
error);
|
||||
break;
|
||||
default:
|
||||
// Other types of CARingBuffer errors should never occur. This will crash debug builds.
|
||||
LogSync_Error("BGMPlayThrough::%s: Ring buffer error: %s (%d)",
|
||||
inMethodName,
|
||||
(error == kCARingBufferError_TooMuch ?
|
||||
"kCARingBufferError_TooMuch" :
|
||||
"unknown error"),
|
||||
error);
|
||||
break;
|
||||
};
|
||||
|
||||
// Reset it.
|
||||
ioRingBufferError = kCARingBufferError_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void* __nullable BGMPlayThroughRTLogger::LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon)
|
||||
{
|
||||
DebugMsg("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: "
|
||||
"Starting the IOProc logging thread");
|
||||
|
||||
while(!inRefCon->mLoggingThreadShouldExit)
|
||||
{
|
||||
// Log the messages, if there are any to log.
|
||||
inRefCon->LogMessages();
|
||||
|
||||
// Wait until woken up.
|
||||
kern_return_t error = semaphore_wait(inRefCon->mWakeUpLoggingThreadSemaphore);
|
||||
BGM_Utils::LogIfMachError("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry",
|
||||
"semaphore_wait",
|
||||
error);
|
||||
}
|
||||
|
||||
DebugMsg("BGMPlayThroughRTLogger::IOProcLoggingThreadEntry: IOProc logging thread exiting");
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if BGM_UnitTest
|
||||
|
||||
#pragma mark Test Helpers
|
||||
|
||||
bool BGMPlayThroughRTLogger::WaitUntilLoggerThreadIdle()
|
||||
{
|
||||
int msWaited = 0;
|
||||
|
||||
while(mLogReleasingWaitingThreadsMsg ||
|
||||
mReleaseWaitingThreadsSignalError != KERN_SUCCESS ||
|
||||
mDroppedFrames.shouldLogMessage ||
|
||||
mNoSamplesReady.shouldLogMessage ||
|
||||
mUnexpectedIOStateAfterStopping.shouldLogMessage ||
|
||||
mRingBufferUnavailable.shouldLogMessage ||
|
||||
mExceptionStoppingIOProc.shouldLogMessage ||
|
||||
mRingBufferStoreError != kCARingBufferError_OK ||
|
||||
mRingBufferFetchError != kCARingBufferError_OK)
|
||||
{
|
||||
// Poll until the logger thread has nothing left to log. (Ideally we'd use a semaphore
|
||||
// instead of polling, but it isn't worth the effort at this point.)
|
||||
usleep(10 * 1000);
|
||||
msWaited += 10;
|
||||
|
||||
// Time out after 5 seconds.
|
||||
if(msWaited > 5000)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* BGM_UnitTest */
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMPlayThroughRTLogger.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
// A real-time safe logger for BGMPlayThrough. The messages are logged asynchronously by a
|
||||
// non-realtime thread.
|
||||
//
|
||||
// For the sake of simplicity, this class is very closely coupled with BGMPlayThrough and its
|
||||
// methods make assumptions about where they will be called. Also, if the same logging method is
|
||||
// called multiple times before the logging thread next checks for messages, it will only log the
|
||||
// message for one of those calls and ignore the others.
|
||||
//
|
||||
// This class's methods are real-time safe in that they return in a bounded amount of time and we
|
||||
// think they're probably fast enough that the callers won't miss their deadlines, but we don't try
|
||||
// to guarantee it. Some of them should only be called in unusual cases where it's worth increasing
|
||||
// the risk of a thread missing its deadline.
|
||||
//
|
||||
|
||||
#ifndef BGMApp__BGMPlayThroughRTLogger
|
||||
#define BGMApp__BGMPlayThroughRTLogger
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CARingBuffer.h"
|
||||
|
||||
// STL Includes
|
||||
#include <thread>
|
||||
|
||||
// System Includes
|
||||
#include <mach/error.h>
|
||||
#include <mach/semaphore.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class BGMPlayThroughRTLogger
|
||||
{
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
public:
|
||||
BGMPlayThroughRTLogger();
|
||||
~BGMPlayThroughRTLogger();
|
||||
BGMPlayThroughRTLogger(const BGMPlayThroughRTLogger&) = delete;
|
||||
BGMPlayThroughRTLogger& operator=(
|
||||
const BGMPlayThroughRTLogger&) = delete;
|
||||
private:
|
||||
static semaphore_t CreateSemaphore();
|
||||
|
||||
#pragma mark Log Messages
|
||||
|
||||
public:
|
||||
/*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
|
||||
void LogReleasingWaitingThreads();
|
||||
/*! For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
|
||||
void LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError);
|
||||
|
||||
/*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */
|
||||
void LogIfDroppedFrames(Float64 inFirstInputSampleTime,
|
||||
Float64 inLastInputSampleTime);
|
||||
/*! For BGMPlayThrough::OutputDeviceIOProc. Not thread-safe. */
|
||||
void LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,
|
||||
CARingBuffer::SampleTime inReadHeadSampleTime,
|
||||
Float64 inInToOutSampleOffset);
|
||||
|
||||
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName)
|
||||
{
|
||||
LogExceptionStoppingIOProc(inCallerName, noErr, false);
|
||||
}
|
||||
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError)
|
||||
{
|
||||
LogExceptionStoppingIOProc(inCallerName, inError, true);
|
||||
}
|
||||
|
||||
private:
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName,
|
||||
OSStatus inError,
|
||||
bool inErrorKnown);
|
||||
|
||||
public:
|
||||
/*! For BGMPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogUnexpectedIOStateAfterStopping(const char* inCallerName,
|
||||
int inIOState);
|
||||
/*! For BGMPlayThrough::InputDeviceIOProc and BGMPlayThrough::OutputDeviceIOProc. */
|
||||
void LogRingBufferUnavailable(const char* inCallerName, bool inGotLock);
|
||||
/*! For BGMPlayThrough::OutputDeviceIOProc. */
|
||||
void LogIfRingBufferError_Fetch(CARingBufferError inError)
|
||||
{
|
||||
LogIfRingBufferError(inError, mRingBufferFetchError);
|
||||
}
|
||||
/*! For BGMPlayThrough::InputDeviceIOProc. */
|
||||
void LogIfRingBufferError_Store(CARingBufferError inError)
|
||||
{
|
||||
LogIfRingBufferError(inError, mRingBufferStoreError);
|
||||
}
|
||||
|
||||
private:
|
||||
void LogIfRingBufferError(CARingBufferError inError,
|
||||
std::atomic<CARingBufferError>& outError);
|
||||
|
||||
template <typename T, typename F>
|
||||
void LogAsync(T& inMessageData, F&& inStoreMessageData);
|
||||
|
||||
// Wrapper methods used to mock out the logging for unit tests.
|
||||
void LogSync_Warning(const char* inFormat, ...) __printflike(2, 3);
|
||||
void LogSync_Error(const char* inFormat, ...) __printflike(2, 3);
|
||||
|
||||
#pragma mark Logging Thread
|
||||
|
||||
private:
|
||||
void WakeLoggingThread();
|
||||
|
||||
void LogMessages();
|
||||
void LogSync_ReleasingWaitingThreads();
|
||||
void LogSync_ReleaseWaitingThreadsSignalError();
|
||||
void LogSync_DroppedFrames();
|
||||
void LogSync_NoSamplesReady();
|
||||
void LogSync_ExceptionStoppingIOProc();
|
||||
void LogSync_UnexpectedIOStateAfterStopping();
|
||||
void LogSync_RingBufferUnavailable();
|
||||
void LogSync_RingBufferError(
|
||||
std::atomic<CARingBufferError>& ioRingBufferError,
|
||||
const char* inMethodName);
|
||||
|
||||
// The entry point of the logging thread (mLoggingThread).
|
||||
static void* __nullable LoggingThreadEntry(BGMPlayThroughRTLogger* inRefCon);
|
||||
|
||||
#if BGM_UnitTest
|
||||
|
||||
#pragma mark Test Helpers
|
||||
|
||||
public:
|
||||
/*!
|
||||
* @return True if the logger thread finished logging the requested messages. False if it still
|
||||
* had messages to log after 5 seconds.
|
||||
*/
|
||||
bool WaitUntilLoggerThreadIdle();
|
||||
|
||||
#endif /* BGM_UnitTest */
|
||||
|
||||
private:
|
||||
// For BGMPlayThrough::ReleaseThreadsWaitingForOutputToStart
|
||||
std::atomic<bool> mLogReleasingWaitingThreadsMsg { false };
|
||||
std::atomic<kern_return_t> mReleaseWaitingThreadsSignalError { KERN_SUCCESS };
|
||||
|
||||
// For BGMPlayThrough::InputDeviceIOProc and BGMPlayThrough::OutputDeviceIOProc
|
||||
struct {
|
||||
Float64 firstInputSampleTime;
|
||||
Float64 lastInputSampleTime;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mDroppedFrames;
|
||||
|
||||
struct {
|
||||
CARingBuffer::SampleTime lastInputSampleTime;
|
||||
CARingBuffer::SampleTime readHeadSampleTime;
|
||||
Float64 inToOutSampleOffset;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mNoSamplesReady;
|
||||
|
||||
struct {
|
||||
const char* callerName;
|
||||
bool gotLock;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mRingBufferUnavailable;
|
||||
|
||||
// For BGMPlayThrough::UpdateIOProcState
|
||||
struct {
|
||||
const char* callerName;
|
||||
int ioState;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mUnexpectedIOStateAfterStopping;
|
||||
|
||||
struct {
|
||||
const char* callerName;
|
||||
OSStatus error;
|
||||
bool errorKnown; // If false, we didn't get an error code from the exception.
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mExceptionStoppingIOProc;
|
||||
|
||||
// For BGMPlayThrough::OutputDeviceIOProc
|
||||
std::atomic<CARingBufferError> mRingBufferStoreError { kCARingBufferError_OK };
|
||||
// For BGMPlayThrough::InputDeviceIOProc.
|
||||
std::atomic<CARingBufferError> mRingBufferFetchError { kCARingBufferError_OK };
|
||||
|
||||
// Signalled to wake up the mLoggingThread when it has messages to log.
|
||||
semaphore_t mWakeUpLoggingThreadSemaphore;
|
||||
std::atomic<bool> mLoggingThreadShouldExit { false };
|
||||
// The thread that actually logs the messages.
|
||||
std::thread mLoggingThread;
|
||||
|
||||
#if BGM_UnitTest
|
||||
|
||||
public:
|
||||
// Tests normally crash (abort) if LogError is called. This flag lets us test the code that
|
||||
// would otherwise call LogError.
|
||||
bool mContinueOnErrorLogged { false };
|
||||
|
||||
int mNumDebugMessagesLogged { 0 };
|
||||
int mNumWarningMessagesLogged { 0 };
|
||||
int mNumErrorMessagesLogged { 0 };
|
||||
|
||||
#endif /* BGM_UnitTest */
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* BGMApp__BGMPlayThroughRTLogger */
|
||||
|
||||
@@ -113,7 +113,7 @@ NSString* const kAudioSystemSettingsPlist =
|
||||
// BGMApp's stored preferred devices to fill in the rest optimistically. This doesn't help us
|
||||
// tell when to switch to a newly connected device, but it should improve our chances of
|
||||
// switching to the best device if the current output device is disconnected.
|
||||
NSArray<NSDictionary*>* preferredOutputDeviceInfos = @[];
|
||||
NSArray<NSDictionary*>* _Nonnull preferredOutputDeviceInfos = @[];
|
||||
|
||||
// If we can't read the Plist, we only know that the current systemwide default device is the
|
||||
// most-preferred device that's currently connected.
|
||||
@@ -125,7 +125,7 @@ NSString* const kAudioSystemSettingsPlist =
|
||||
BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
|
||||
BGMAudioDevice defaultDevice = CAHALAudioSystemObject().GetDefaultAudioDevice(false, false);
|
||||
NSString* __nullable defaultDeviceUID =
|
||||
(__bridge NSString* __nullable)defaultDevice.CopyDeviceUID();
|
||||
(__bridge_transfer NSString* __nullable)defaultDevice.CopyDeviceUID();
|
||||
|
||||
if (defaultDeviceUID) {
|
||||
preferredOutputDeviceInfos = @[ @{ @"uid": BGMNN(defaultDeviceUID) } ];
|
||||
@@ -459,7 +459,7 @@ NSString* const kAudioSystemSettingsPlist =
|
||||
BGM_Utils::LogAndSwallowExceptions(BGMDbgArgs, [&] {
|
||||
// Add the new output device to the list.
|
||||
NSString* __nullable outputDeviceUID =
|
||||
(__bridge NSString* __nullable)CAHALAudioDevice(device).CopyDeviceUID();
|
||||
(__bridge_transfer NSString* __nullable)CAHALAudioDevice(device).CopyDeviceUID();
|
||||
|
||||
if (outputDeviceUID) {
|
||||
// Limit the list to three devices because that's what macOS does.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMStatusBarItem.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
// Copyright © 2019, 2020 Kyle Neideck
|
||||
//
|
||||
// The button in the system status bar (the bar with volume, battery, clock, etc.) to show the main
|
||||
// menu for the app. These are called "menu bar extras" in the Human Interface Guidelines.
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
#import "BGMDebugLoggingMenuItem.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
@@ -57,6 +58,10 @@ static BGMStatusBarIcon const kBGMStatusBarIconDefaultValue = BGMFermataStatusBa
|
||||
// same as the icon for the macOS volume status bar item.
|
||||
@property BGMStatusBarIcon icon;
|
||||
|
||||
// If the user holds down the option key when they click the status bar icon, this menu item will be
|
||||
// shown in the main menu.
|
||||
- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMStatusBarItem.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
// Copyright © 2019, 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -48,11 +48,16 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
NSImage* volumeIcon3SoundWaves;
|
||||
|
||||
NSStatusItem* statusBarItem;
|
||||
BGMDebugLoggingMenuItem* debugLoggingMenuItem;
|
||||
|
||||
BGMVolumeChangeListener* volumeChangeListener;
|
||||
id __nullable clickEventHandler;
|
||||
|
||||
BGMStatusBarIcon _icon;
|
||||
}
|
||||
|
||||
#pragma mark Initialisation
|
||||
|
||||
- (instancetype) initWithMenu:(NSMenu*)bgmMenu
|
||||
audioDevices:(BGMAudioDeviceManager*)devices
|
||||
userDefaults:(BGMUserDefaults*)defaults {
|
||||
@@ -72,6 +77,10 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
// Set the menu item to open the main menu.
|
||||
statusBarItem.menu = bgmMenu;
|
||||
|
||||
// Monitor click events so we can show extra options in the menu if the user was holding the
|
||||
// option key.
|
||||
clickEventHandler = [self addClickMonitor];
|
||||
|
||||
// Set the accessibility label to "Background Music". (We intentionally don't set a title or
|
||||
// a tooltip.)
|
||||
if ([BGMStatusBarItem buttonAvailable]) {
|
||||
@@ -84,7 +93,7 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
|
||||
// Update the icon when BGMDevice's volume changes.
|
||||
BGMStatusBarItem* __weak weakSelf = self;
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [&] {
|
||||
volumeChangeListener = new BGMVolumeChangeListener(audioDevices.bgmDevice, [=] {
|
||||
[weakSelf bgmDeviceVolumeDidChange];
|
||||
});
|
||||
}
|
||||
@@ -92,17 +101,41 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id __nullable) addClickMonitor {
|
||||
NSEvent* __nullable (^handlerBlock)(NSEvent*) =
|
||||
^NSEvent* __nullable (NSEvent* event) {
|
||||
[self statusBarItemWasClicked:event];
|
||||
return event;
|
||||
};
|
||||
|
||||
// TODO: I doubt this works well with VoiceOver.
|
||||
return [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown
|
||||
handler:handlerBlock];
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
delete volumeChangeListener;
|
||||
|
||||
if (clickEventHandler) {
|
||||
[NSEvent removeMonitor:(id)clickEventHandler];
|
||||
clickEventHandler = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) initIcons {
|
||||
// Load the icons.
|
||||
fermataIcon = [NSImage imageNamed:@"FermataIcon"];
|
||||
volumeIcon0SoundWaves = [NSImage imageNamed:@"Volume0"];
|
||||
volumeIcon1SoundWave = [NSImage imageNamed:@"Volume1"];
|
||||
volumeIcon2SoundWaves = [NSImage imageNamed:@"Volume2"];
|
||||
volumeIcon3SoundWaves = [NSImage imageNamed:@"Volume3"];
|
||||
if (@available(macOS 11.0, *)) {
|
||||
volumeIcon0SoundWaves = [NSImage imageWithSystemSymbolName:@"speaker.fill" accessibilityDescription:nil];
|
||||
volumeIcon1SoundWave = [NSImage imageWithSystemSymbolName:@"speaker.wave.1.fill" accessibilityDescription:nil];
|
||||
volumeIcon2SoundWaves = [NSImage imageWithSystemSymbolName:@"speaker.wave.2.fill" accessibilityDescription:nil];
|
||||
volumeIcon3SoundWaves = [NSImage imageWithSystemSymbolName:@"speaker.wave.3.fill" accessibilityDescription:nil];
|
||||
} else {
|
||||
volumeIcon0SoundWaves = [NSImage imageNamed:@"Volume0"];
|
||||
volumeIcon1SoundWave = [NSImage imageNamed:@"Volume1"];
|
||||
volumeIcon2SoundWaves = [NSImage imageNamed:@"Volume2"];
|
||||
volumeIcon3SoundWaves = [NSImage imageNamed:@"Volume3"];
|
||||
}
|
||||
|
||||
// Set the icons' sizes.
|
||||
NSRect statusBarItemFrame;
|
||||
@@ -142,6 +175,8 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
[volumeIcon3SoundWaves setTemplate:YES];
|
||||
}
|
||||
|
||||
#pragma mark Accessors
|
||||
|
||||
+ (BOOL) buttonAvailable {
|
||||
// NSStatusItem doesn't have the "button" property on OS X 10.9.
|
||||
return (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10);
|
||||
@@ -188,6 +223,8 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark Volume Icon
|
||||
|
||||
- (void) bgmDeviceVolumeDidChange {
|
||||
if (self.icon == BGMVolumeStatusBarIcon) {
|
||||
[self updateVolumeStatusBarIcon];
|
||||
@@ -251,6 +288,21 @@ static CGFloat const kVolumeIconAdditionalVerticalPadding = 0.075;
|
||||
statusBarItem.image.name.UTF8String);
|
||||
}
|
||||
|
||||
#pragma mark Debug Logging Menu Item
|
||||
|
||||
- (void) statusBarItemWasClicked:(NSEvent* __nonnull)event {
|
||||
if ((event.modifierFlags & NSEventModifierFlagOption) != 0) {
|
||||
DebugMsg("BGMStatusBarItem::statusBarItemWasClicked: Option key held");
|
||||
[debugLoggingMenuItem setMenuShowingExtraOptions:YES];
|
||||
} else {
|
||||
[debugLoggingMenuItem setMenuShowingExtraOptions:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) setDebugLoggingMenuItem:(BGMDebugLoggingMenuItem*)menuItem {
|
||||
debugLoggingMenuItem = menuItem;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
//
|
||||
// Cleans up if BGMApp crashes because of an uncaught C++ or Objective C exception, or is sent
|
||||
// Cleans up if BGMApp crashes because of an uncaught C++ or Objective-C exception, or is sent
|
||||
// SIGINT/SIGTERM/SIGQUIT. Currently, it just changes the default output device from BGMDevice to
|
||||
// the real output device and records debug info for some types of crashes.
|
||||
//
|
||||
|
||||
@@ -83,7 +83,7 @@ void BGMTermination::SetUpTerminationCleanUp(BGMAudioDeviceManager* inAudioDevic
|
||||
StartExitSignalsThread();
|
||||
|
||||
// Wrap the default handler for std::terminate, which is called if BGMApp crashes because of an
|
||||
// uncaught C++ or Objective C exception, so we can clean up first.
|
||||
// uncaught C++ or Objective-C exception, so we can clean up first.
|
||||
sOriginalTerminateHandler = std::get_terminate();
|
||||
|
||||
std::set_terminate([] {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
// A simple wrapper around our use of NSUserDefaults. Used to store the preferences/state that only
|
||||
// apply to BGMApp. The others are stored by BGMDriver.
|
||||
//
|
||||
// Private data will be stored in the user's keychain instead of user defaults.
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#import "BGMStatusBarItem.h"
|
||||
@@ -51,6 +53,13 @@
|
||||
// BGMApp's main menu.)
|
||||
@property BGMStatusBarIcon statusBarIcon;
|
||||
|
||||
// The auth code we're required to send when connecting to GPMDP. Stored in the keychain. Reading
|
||||
// this property is thread-safe, but writing it isn't.
|
||||
//
|
||||
// Returns nil if no code is found or if reading fails. If writing fails, an error is logged, but no
|
||||
// exception is thrown.
|
||||
@property NSString* __nullable googlePlayMusicDesktopPlayerPermanentAuthCode;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
+109
-15
@@ -30,10 +30,14 @@
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
// Keys
|
||||
static NSString* const BGMDefaults_AutoPauseMusicEnabled = @"AutoPauseMusicEnabled";
|
||||
static NSString* const BGMDefaults_SelectedMusicPlayerID = @"SelectedMusicPlayerID";
|
||||
static NSString* const BGMDefaults_PreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||
static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||
static NSString* const kDefaultKeyAutoPauseMusicEnabled = @"AutoPauseMusicEnabled";
|
||||
static NSString* const kDefaultKeySelectedMusicPlayerID = @"SelectedMusicPlayerID";
|
||||
static NSString* const kDefaultKeyPreferredDeviceUIDs = @"PreferredDeviceUIDs";
|
||||
static NSString* const kDefaultKeyStatusBarIcon = @"StatusBarIcon";
|
||||
|
||||
// Labels for Keychain Data
|
||||
static NSString* const kKeychainLabelGPMDPAuthCode =
|
||||
@"app.backgroundmusic: Google Play Music Desktop Player permanent auth code";
|
||||
|
||||
@implementation BGMUserDefaults {
|
||||
// The defaults object wrapped by this object.
|
||||
@@ -49,11 +53,11 @@ static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||
|
||||
// Register the settings defaults.
|
||||
//
|
||||
// iTunes is the default music player, but we don't set BGMDefaults_SelectedMusicPlayerID
|
||||
// iTunes is the default music player, but we don't set kDefaultKeySelectedMusicPlayerID
|
||||
// here so we know when it's never been set. (If it hasn't, we try using BGMDevice's
|
||||
// kAudioDeviceCustomPropertyMusicPlayerBundleID property to tell which music player should
|
||||
// be selected. See BGMMusicPlayers.)
|
||||
NSDictionary* defaultsDict = @{ BGMDefaults_AutoPauseMusicEnabled: @YES };
|
||||
NSDictionary* defaultsDict = @{ kDefaultKeyAutoPauseMusicEnabled: @YES };
|
||||
|
||||
if (defaults) {
|
||||
[defaults registerDefaults:defaultsDict];
|
||||
@@ -65,33 +69,37 @@ static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Selected Music Player
|
||||
|
||||
- (NSString* __nullable) selectedMusicPlayerID {
|
||||
return [self get:BGMDefaults_SelectedMusicPlayerID];
|
||||
return [self get:kDefaultKeySelectedMusicPlayerID];
|
||||
}
|
||||
|
||||
- (void) setSelectedMusicPlayerID:(NSString* __nullable)selectedMusicPlayerID {
|
||||
[self set:BGMDefaults_SelectedMusicPlayerID to:selectedMusicPlayerID];
|
||||
[self set:kDefaultKeySelectedMusicPlayerID to:selectedMusicPlayerID];
|
||||
}
|
||||
|
||||
#pragma mark Auto-pause
|
||||
|
||||
- (BOOL) autoPauseMusicEnabled {
|
||||
return [self getBool:BGMDefaults_AutoPauseMusicEnabled];
|
||||
return [self getBool:kDefaultKeyAutoPauseMusicEnabled];
|
||||
}
|
||||
|
||||
- (void) setAutoPauseMusicEnabled:(BOOL)autoPauseMusicEnabled {
|
||||
[self setBool:BGMDefaults_AutoPauseMusicEnabled to:autoPauseMusicEnabled];
|
||||
[self setBool:kDefaultKeyAutoPauseMusicEnabled to:autoPauseMusicEnabled];
|
||||
}
|
||||
|
||||
- (NSArray<NSString*>*) preferredDeviceUIDs {
|
||||
NSArray<NSString*>* __nullable uids = [self get:BGMDefaults_PreferredDeviceUIDs];
|
||||
NSArray<NSString*>* __nullable uids = [self get:kDefaultKeyPreferredDeviceUIDs];
|
||||
return uids ? BGMNN(uids) : @[];
|
||||
}
|
||||
|
||||
- (void) setPreferredDeviceUIDs:(NSArray<NSString*>*)devices {
|
||||
[self set:BGMDefaults_PreferredDeviceUIDs to:devices];
|
||||
[self set:kDefaultKeyPreferredDeviceUIDs to:devices];
|
||||
}
|
||||
|
||||
- (BGMStatusBarIcon) statusBarIcon {
|
||||
NSInteger icon = [self getInt:BGMDefaults_StatusBarIcon or:kBGMStatusBarIconDefaultValue];
|
||||
NSInteger icon = [self getInt:kDefaultKeyStatusBarIcon or:kBGMStatusBarIconDefaultValue];
|
||||
|
||||
// Just in case we get an invalid value somehow.
|
||||
if ((icon < kBGMStatusBarIconMinValue) || (icon > kBGMStatusBarIconMaxValue)) {
|
||||
@@ -103,10 +111,96 @@ static NSString* const BGMDefaults_StatusBarIcon = @"StatusBarIcon";
|
||||
}
|
||||
|
||||
- (void) setStatusBarIcon:(BGMStatusBarIcon)icon {
|
||||
[self setInt:BGMDefaults_StatusBarIcon to:icon];
|
||||
[self setInt:kDefaultKeyStatusBarIcon to:icon];
|
||||
}
|
||||
|
||||
#pragma mark Implementation
|
||||
#pragma mark Google Play Music Desktop Player
|
||||
|
||||
- (NSString* __nullable) googlePlayMusicDesktopPlayerPermanentAuthCode {
|
||||
// Try to read the permanent auth code from the user's keychain.
|
||||
NSDictionary<NSString*, NSObject*>* query = @{
|
||||
(__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,
|
||||
(__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode,
|
||||
(__bridge NSString*)kSecMatchLimit: (__bridge NSString*)kSecMatchLimitOne,
|
||||
(__bridge NSString*)kSecReturnData: @YES
|
||||
};
|
||||
|
||||
CFTypeRef result = nil;
|
||||
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
||||
|
||||
NSString* __nullable authCode = nil;
|
||||
|
||||
// Check the return status, null check and check the type.
|
||||
if ((err == errSecSuccess) && result && (CFGetTypeID(result) == CFDataGetTypeID())) {
|
||||
// Convert it to a string.
|
||||
CFStringRef __nullable code =
|
||||
CFStringCreateFromExternalRepresentation(kCFAllocatorDefault,
|
||||
result,
|
||||
kCFStringEncodingUTF8);
|
||||
authCode = (__bridge_transfer NSString* __nullable)code;
|
||||
} else if (err != errSecItemNotFound) {
|
||||
NSString* __nullable errMsg =
|
||||
(__bridge_transfer NSString* __nullable)SecCopyErrorMessageString(err, nil);
|
||||
NSLog(@"Failed to read GPMDP auth code from keychain: %d, %@", err, errMsg);
|
||||
}
|
||||
|
||||
// Release the data we read.
|
||||
if (result) {
|
||||
CFRelease(result);
|
||||
}
|
||||
|
||||
return authCode;
|
||||
}
|
||||
|
||||
- (void) setGooglePlayMusicDesktopPlayerPermanentAuthCode:(NSString* __nullable)authCode {
|
||||
if (authCode) {
|
||||
// Convert it to an NSData so we can store it in the user's keychain.
|
||||
NSData* authCodeData = [authCode dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
// Delete the old code if necessary. (There's an update function, but this takes less code.)
|
||||
if (self.googlePlayMusicDesktopPlayerPermanentAuthCode) {
|
||||
[self deleteGPMDPPermanentAuthCode];
|
||||
}
|
||||
|
||||
// Store the code.
|
||||
[self addGPMDPPermanentAuthCode:authCodeData];
|
||||
} else {
|
||||
[self deleteGPMDPPermanentAuthCode];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) addGPMDPPermanentAuthCode:(NSData*)authCodeData {
|
||||
NSDictionary<NSString*, NSObject*>* attributes = @{
|
||||
(__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,
|
||||
(__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode,
|
||||
(__bridge NSString*)kSecValueData: authCodeData
|
||||
};
|
||||
|
||||
OSStatus err = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);
|
||||
|
||||
// Just log an error if it failed.
|
||||
if (err != errSecSuccess) {
|
||||
NSString* errMsg = (__bridge_transfer NSString*)SecCopyErrorMessageString(err, nil);
|
||||
NSLog(@"Failed to store GPMDP auth code in keychain: %d, %@", err, errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) deleteGPMDPPermanentAuthCode {
|
||||
NSDictionary<NSString*, NSObject*>* query = @{
|
||||
(__bridge NSString*)kSecClass: (__bridge NSString*)kSecClassGenericPassword,
|
||||
(__bridge NSString*)kSecAttrLabel: kKeychainLabelGPMDPAuthCode
|
||||
};
|
||||
|
||||
OSStatus err = SecItemDelete((__bridge CFDictionaryRef)query);
|
||||
|
||||
// Just log an error if it failed.
|
||||
if (err != errSecSuccess) {
|
||||
NSString* errMsg = (__bridge_transfer NSString*)SecCopyErrorMessageString(err, nil);
|
||||
NSLog(@"Failed to delete GPMDP auth code from keychain: %d, %@", err, errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark General Accessors
|
||||
|
||||
- (id __nullable) get:(NSString*)key {
|
||||
return defaults ? [defaults objectForKey:key] : transientDefaults[key];
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<development version="8000" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -21,6 +20,7 @@
|
||||
<outlet property="appVolumeView" destination="MWB-XH-kFI" id="eFA-RN-VMC"/>
|
||||
<outlet property="autoPauseMenuItemUnwrapped" destination="nHv-T8-1nb" id="Lie-Cx-jw6"/>
|
||||
<outlet property="bgmMenu" destination="8AN-nh-rEe" id="UWn-BX-eLy"/>
|
||||
<outlet property="debugLoggingMenuItemUnwrapped" destination="sc9-vO-KyP" id="Zyd-0v-0RN"/>
|
||||
<outlet property="outputVolumeLabel" destination="wfC-C6-SLv" id="Nuf-mo-osG"/>
|
||||
<outlet property="outputVolumeSlider" destination="9Ru-Sc-dqC" id="wv0-Md-BwF"/>
|
||||
<outlet property="outputVolumeView" destination="JOz-H1-mj9" id="xeJ-fk-NMI"/>
|
||||
@@ -58,7 +58,7 @@
|
||||
<menuItem title="Background Music Logo" state="on" tag="2" id="9VF-qy-6fh">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Volume Icon" tag="3" toolTip="todo" id="B47-O2-wd0">
|
||||
<menuItem title="Volume Icon" tag="3" id="B47-O2-wd0">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="pYP-Fy-nKA"/>
|
||||
@@ -68,6 +68,9 @@
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Debug Logging" hidden="YES" toolTip="Log detailed messages to help diagnose bugs. Search for "bgm" or "background music" in Console.app." id="sc9-vO-KyP">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="Quit Background Music" id="Nj2-gJ-DhW">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
@@ -82,11 +85,11 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="269" height="47"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField identifier="AppName" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xmd-bg-huG" customClass="BGMAVM_AppNameLabel">
|
||||
<textField identifier="AppName" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xmd-bg-huG" customClass="BGMAVM_AppNameLabel">
|
||||
<rect key="frame" x="42" y="28" width="115" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" title="App name here" usesSingleLineMode="YES" id="ZHF-ZW-Oqg">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@@ -99,7 +102,7 @@
|
||||
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="I1l-Ci-4md" customClass="BGMAVM_VolumeSlider">
|
||||
<rect key="frame" x="163" y="27" width="74" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="100" doubleValue="50" tickMarkPosition="above" sliderType="linear" id="Jmg-df-9Xl"/>
|
||||
<sliderCell key="cell" controlSize="mini" continuous="YES" state="on" alignment="left" maxValue="100" doubleValue="50" tickMarkPosition="above" sliderType="linear" id="Jmg-df-9Xl"/>
|
||||
<accessibility description="Volume"/>
|
||||
</slider>
|
||||
<slider toolTip="Pan" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2mh-uO-kOV" customClass="BGMAVM_PanSlider">
|
||||
@@ -108,7 +111,7 @@
|
||||
<sliderCell key="cell" controlSize="mini" continuous="YES" state="on" alignment="left" minValue="-100" maxValue="100" tickMarkPosition="below" numberOfTickMarks="1" sliderType="linear" id="ccM-Mt-93g"/>
|
||||
<accessibility description="Pan"/>
|
||||
</slider>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" tag="1" springLoaded="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vTG-n6-GxY" customClass="BGMAVM_ShowMoreControlsButton">
|
||||
<button tag="1" springLoaded="YES" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vTG-n6-GxY" customClass="BGMAVM_ShowMoreControlsButton">
|
||||
<rect key="frame" x="243" y="27" width="16" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<contentFilters>
|
||||
@@ -124,20 +127,20 @@
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<textField identifier="PanLeft" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9jc-9i-jw2">
|
||||
<textField identifier="PanLeft" toolTip="Pan" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9jc-9i-jw2">
|
||||
<rect key="frame" x="162" y="-1" width="12" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="L" id="hgE-7A-bez">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<font key="font" metaFont="menu" size="9"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField identifier="PanRight" toolTip="Pan" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1lZ-hX-6Kl">
|
||||
<textField identifier="PanRight" toolTip="Pan" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1lZ-hX-6Kl">
|
||||
<rect key="frame" x="228" y="-1" width="12" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="R" id="lzr-NO-0Na">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<font key="font" metaFont="menu" size="9"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@@ -145,34 +148,34 @@
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="117" y="-45"/>
|
||||
</customView>
|
||||
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" visibleAtLaunch="NO" animationBehavior="default" id="Cf4-3V-gl1" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES"/>
|
||||
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="Cf4-3V-gl1" customClass="NSPanel">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" topStrut="YES"/>
|
||||
<rect key="contentRect" x="248" y="350" width="1002" height="335"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="920"/>
|
||||
<view key="contentView" id="HlB-hX-Y0Y">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1002" height="335"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r51-dd-LGP">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r51-dd-LGP">
|
||||
<rect key="frame" x="71" y="125" width="240" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Background Music" id="Dw2-nu-eBQ">
|
||||
<font key="font" size="18" name=".HelveticaNeueDeskInterface-Regular"/>
|
||||
<font key="font" metaFont="system" size="18"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="1" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ekc-h0-I43">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="1" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ekc-h0-I43">
|
||||
<rect key="frame" x="71" y="100" width="240" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Version 0.3.0" id="FDH-7l-wFf">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Version 0.4.3" id="FDH-7l-wFf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L5P-Lw-aCd">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L5P-Lw-aCd">
|
||||
<rect key="frame" x="413" y="298" width="270" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="Licensed under GPL v2 or any later version." id="ETh-En-bzX">
|
||||
@@ -185,7 +188,7 @@
|
||||
<rect key="frame" x="383" y="93" width="5" height="150"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="3" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nx6-kQ-N8Z" customClass="BGMLinkField">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="3" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nx6-kQ-N8Z" customClass="BGMLinkField">
|
||||
<rect key="frame" x="36" y="50" width="310" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" tag="3" title="https://github.com/kyleneideck/BackgroundMusic" placeholderString="" id="VOb-5X-o3R">
|
||||
@@ -213,11 +216,11 @@
|
||||
<scrollView fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eqz-ap-PAC">
|
||||
<rect key="frame" x="415" y="45" width="567" height="245"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="Cdb-RA-YK0">
|
||||
<clipView key="contentView" drawsBackground="NO" id="Cdb-RA-YK0">
|
||||
<rect key="frame" x="1" y="1" width="565" height="243"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView ambiguous="YES" editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="LSG-PF-cl8">
|
||||
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" id="LSG-PF-cl8">
|
||||
<rect key="frame" x="-6" y="0.0" width="577" height="243"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -240,29 +243,38 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6qu-yI-r00">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6qu-yI-r00">
|
||||
<rect key="frame" x="413" y="20" width="203" height="11"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="The AirPlay Logo is a trademark of Apple Inc." id="lx7-k3-q16">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<font key="font" metaFont="menu" size="9"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="2" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vy4-dv-jQB">
|
||||
<rect key="frame" x="18" y="75" width="346" height="17"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="2" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vy4-dv-jQB">
|
||||
<rect key="frame" x="18" y="75" width="155" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Copyright © 2016-2018 Background Music contributors" placeholderString="" id="ctF-95-uVu">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Copyright © 2016-2024" placeholderString="" id="ctF-95-uVu">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" tag="4" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tRj-QC-GuQ" customClass="BGMLinkField">
|
||||
<rect key="frame" x="168" y="75" width="194" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" tag="3" title="Background Music contributors" placeholderString="" allowsEditingTextAttributes="YES" usesSingleLineMode="YES" id="UC2-MX-fML">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.20000000000000001" green="0.40000000000000002" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="-200" y="232.5"/>
|
||||
</window>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" allowsCharacterPickerTouchBarItem="YES" id="IoN-sN-cCx">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" allowsCharacterPickerTouchBarItem="YES" id="IoN-sN-cCx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="471" height="180"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" controlSize="mini" sendsActionOnEndEditing="YES" drawsBackground="YES" id="Ay8-8n-FHi">
|
||||
@@ -281,10 +293,10 @@
|
||||
<slider identifier="Output Volume" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9Ru-Sc-dqC" userLabel="Output Volume Slider">
|
||||
<rect key="frame" x="20" y="4" width="220" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<sliderCell key="cell" continuous="YES" state="on" alignment="left" maxValue="1" tickMarkPosition="above" sliderType="linear" id="MzM-fe-nKb"/>
|
||||
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="1" tickMarkPosition="above" sliderType="linear" id="MzM-fe-nKb"/>
|
||||
<accessibility description="Output Volume" help="Sets the volume of your audio output device." identifier="Output Volume"/>
|
||||
</slider>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wfC-C6-SLv">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wfC-C6-SLv">
|
||||
<rect key="frame" x="20" y="25" width="226" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Volume" id="60O-ju-B5C">
|
||||
@@ -303,14 +315,14 @@
|
||||
<slider identifier="System Sounds Volume" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gyd-WV-2ju" userLabel="Output Volume Slider">
|
||||
<rect key="frame" x="163" y="0.0" width="74" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<sliderCell key="cell" controlSize="small" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="VDn-d8-XK3"/>
|
||||
<sliderCell key="cell" controlSize="mini" continuous="YES" state="on" alignment="left" maxValue="1" doubleValue="1" tickMarkPosition="above" sliderType="linear" id="VDn-d8-XK3"/>
|
||||
<accessibility description="System Sounds Volume" help="Volume of alerts, notification sounds, etc. Usually short. Can be played by any app."/>
|
||||
</slider>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iKs-df-Hp6">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iKs-df-Hp6">
|
||||
<rect key="frame" x="42" y="1" width="86" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="System Sounds" id="ATK-L8-s8z">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@@ -329,54 +341,55 @@
|
||||
<image name="NSComputer" width="32" height="32"/>
|
||||
<image name="buttonCell:IXo-C7-3uE:image" width="1" height="1">
|
||||
<mutableData key="keyedArchiveRepresentation">
|
||||
YnBsaXN0MDDUAQIDBAUGPT5YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK4HCBMU
|
||||
GR4fIyQrLjE3OlUkbnVsbNUJCgsMDQ4PEBESVk5TU2l6ZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVw
|
||||
c1dOU0NvbG9ygAKADRIgwwAAgAOAC1Z7MSwgMX3SFQoWGFpOUy5vYmplY3RzoReABIAK0hUKGh2iGxyA
|
||||
BYAGgAkQANIgCiEiXxAUTlNUSUZGUmVwcmVzZW50YXRpb26AB4AITxEIxE1NACoAAAAKAAAAEAEAAAMA
|
||||
AAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEKAAMA
|
||||
AAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEWAAMAAAABAAEAAAEXAAQA
|
||||
AAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFTAAMAAAACAAEAAYdzAAcA
|
||||
AAf0AAAA0AAAAAAAAAf0YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAAAABhY3NwQVBQTAAAAABu
|
||||
b25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAABmZjcHJ0AAAHmAAAADh3
|
||||
dHB0AAAH0AAAABRrVFJDAAAH5AAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdyYXkgUHJvZmlsZQAAAAAA
|
||||
AAAAAAAAFUdlbmVyaWMgR3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNrU0sAAAAqAAABhGVuVVMAAAAoAAABrmNhRVMA
|
||||
AAAsAAAB1nZpVk4AAAAsAAACAnB0QlIAAAAqAAACLnVrVUEAAAAsAAACWGZyRlUAAAAqAAAChGh1SFUA
|
||||
AAAuAAACrnpoVFcAAAAQAAAC3G5iTk8AAAAsAAAC7GtvS1IAAAAYAAADGGNzQ1oAAAAkAAADMGhlSUwA
|
||||
AAAgAAADVHJvUk8AAAAkAAADdGRlREUAAAA6AAADmGl0SVQAAAAuAAAD0nN2U0UAAAAuAAAEAHpoQ04A
|
||||
AAAQAAAELmphSlAAAAAWAAAEPmVsR1IAAAAkAAAEVHB0UE8AAAA4AAAEeG5sTkwAAAAqAAAEsGVzRVMA
|
||||
AAAoAAAE2nRoVEgAAAAkAAAFAnRyVFIAAAAiAAAFJmZpRkkAAAAsAAAFSGhySFIAAAA6AAAFdHBsUEwA
|
||||
AAA2AAAFrnJ1UlUAAAAmAAAF5GFyRUcAAAAoAAAGCmRhREsAAAA0AAAGMgBWAWEAZQBvAGIAZQBjAG4A
|
||||
/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAFAAcgBvAGYA
|
||||
aQBsAGUAUABlAHIAZgBpAGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6AByAGkAYwBDHqUAdQAgAGgA
|
||||
7ABuAGgAIABNAOAAdQAgAHgA4QBtACAAQwBoAHUAbgBnAFAAZQByAGYAaQBsACAAQwBpAG4AegBhACAA
|
||||
RwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAEcAcgBhAHkA
|
||||
UAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAGcAcgBpAHMAwQBsAHQAYQBsAOEAbgBvAHMA
|
||||
IABzAHoA/AByAGsAZQAgAHAAcgBvAGYAaQBskBp1KHBwlo6Ccl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsA
|
||||
IABnAHIA5QB0AG8AbgBlAHAAcgBvAGYAaQBsx3y8GAAgAEcAcgBhAHkAINUEuFzTDMd8AE8AYgBlAGMA
|
||||
bgD9ACABYQBlAGQA/QAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAARwByAGEAeQAgBdsF3AXcBdkA
|
||||
UAByAG8AZgBpAGwAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAA
|
||||
RwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBvAGYAaQBsAFAAcgBvAGYAaQBsAG8AIABnAHIAaQBnAGkA
|
||||
bwAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QBzAGsAYQBsAGUAcAByAG8A
|
||||
ZgBpAGxmbpAacHBepmPPj/Blh072TgCCLDCwMOwwpDDXMO0w1TChMKQw6wOTA7UDvQO5A7oDzAAgA8AD
|
||||
wQO/A8YDrwO7ACADswO6A8EDuQBQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGMA
|
||||
aQBuAHoAZQBuAHQAbwBzAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwBwAHIAbwBmAGkAZQBsAFAA
|
||||
ZQByAGYAaQBsACAAZwByAGkAcwAgAGcAZQBuAOkAcgBpAGMAbw5CDhsOIw5EDh8OJQ5MDioONQ5ADhcO
|
||||
Mg4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABHAHIAaQAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA
|
||||
bgAgAGgAYQByAG0AYQBhAHAAcgBvAGYAaQBpAGwAaQBHAGUAbgBlAHIAaQENAGsAaQAgAHAAcgBvAGYA
|
||||
aQBsACAAcwBpAHYAaQBoACAAdABvAG4AbwB2AGEAVQBuAGkAdwBlAHIAcwBhAGwAbgB5ACAAcAByAG8A
|
||||
ZgBpAGwAIABzAHoAYQByAG8BWwBjAGkEHgQxBEkEOAQ5ACAEQQQ1BEAESwQ5ACAEPwRABD4ERAQ4BDsE
|
||||
TAZFBkQGQQAgBioGOQYxBkoGQQAgAEcAcgBhAHkAIAYnBkQGOQYnBkUARwBlAG4AZQByAGUAbAAgAGcA
|
||||
cgDlAHQAbwBuAGUAYgBlAHMAawByAGkAdgBlAGwAcwBlAAB0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFw
|
||||
cGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAA
|
||||
AAABAc0AANIlJicoWiRjbGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoycpKlpOU0lt
|
||||
YWdlUmVwWE5TT2JqZWN00iUmLC1XTlNBcnJheaIsKtIlJi8wXk5TTXV0YWJsZUFycmF5oy8sKtMyMwo0
|
||||
NTZXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzSJSY4OVdOU0NvbG9yojgq0iUmOzxXTlNJbWFn
|
||||
ZaI7Kl8QD05TS2V5ZWRBcmNoaXZlctE/QFRyb290gAEACAARABoAIwAtADIANwBGAEwAVwBeAGUAcgB5
|
||||
AIEAgwCFAIoAjACOAJUAmgClAKcAqQCrALAAswC1ALcAuQC7AMAA1wDZANsJowmoCbMJvAnPCdMJ3gnn
|
||||
CewJ9An3CfwKCwoPChYKHgorCjAKMgo0CjkKQQpECkkKUQpUCmYKaQpuAAAAAAAAAgEAAAAAAAAAQQAA
|
||||
AAAAAAAAAAAAAAAACnA
|
||||
YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
|
||||
S2V5ZWRBcmNoaXZlctEICVRyb290gAGuCwwZGh8UJCkqMTQ3PUBVJG51bGzWDQ4PEBESExQVFhcYVk5T
|
||||
U2l6ZV5OU1Jlc2l6aW5nTW9kZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVwc1dOU0NvbG9ygAIQAIAN
|
||||
EiDDAACAA4ALVnsxLCAxfdIbDxweWk5TLm9iamVjdHOhHYAEgArSGw8gI6IhIoAFgAaACdMPJSYnKBRf
|
||||
EBROU1RJRkZSZXByZXNlbnRhdGlvbl8QGU5TSW50ZXJuYWxMYXlvdXREaXJlY3Rpb26ACIAHTxEIxE1N
|
||||
ACoAAAAKAAAAEAEAAAMAAAABAAEAAAEBAAMAAAABAAEAAAECAAMAAAACAAgACAEDAAMAAAABAAEAAAEG
|
||||
AAMAAAABAAEAAAEKAAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAIAAAEW
|
||||
AAMAAAABAAEAAAEXAAQAAAABAAAAAgEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFSAAMAAAABAAEAAAFT
|
||||
AAMAAAACAAEAAYdzAAcAAAf0AAAA0AAAAAAAAAf0YXBwbAIgAABtbnRyR1JBWVhZWiAH0AACAA4ADAAA
|
||||
AABhY3NwQVBQTAAAAABub25lAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVkZXNjAAAAwAAAAG9kc2NtAAABMAAA
|
||||
BmZjcHJ0AAAHmAAAADh3dHB0AAAH0AAAABRrVFJDAAAH5AAAAA5kZXNjAAAAAAAAABVHZW5lcmljIEdy
|
||||
YXkgUHJvZmlsZQAAAAAAAAAAAAAAFUdlbmVyaWMgR3JheSBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNrU0sAAAAqAAABhGVu
|
||||
VVMAAAAoAAABrmNhRVMAAAAsAAAB1nZpVk4AAAAsAAACAnB0QlIAAAAqAAACLnVrVUEAAAAsAAACWGZy
|
||||
RlUAAAAqAAAChGh1SFUAAAAuAAACrnpoVFcAAAAQAAAC3G5iTk8AAAAsAAAC7GtvS1IAAAAYAAADGGNz
|
||||
Q1oAAAAkAAADMGhlSUwAAAAgAAADVHJvUk8AAAAkAAADdGRlREUAAAA6AAADmGl0SVQAAAAuAAAD0nN2
|
||||
U0UAAAAuAAAEAHpoQ04AAAAQAAAELmphSlAAAAAWAAAEPmVsR1IAAAAkAAAEVHB0UE8AAAA4AAAEeG5s
|
||||
TkwAAAAqAAAEsGVzRVMAAAAoAAAE2nRoVEgAAAAkAAAFAnRyVFIAAAAiAAAFJmZpRkkAAAAsAAAFSGhy
|
||||
SFIAAAA6AAAFdHBsUEwAAAA2AAAFrnJ1UlUAAAAmAAAF5GFyRUcAAAAoAAAGCmRhREsAAAA0AAAGMgBW
|
||||
AWEAZQBvAGIAZQBjAG4A/QAgAHMAaQB2AP0AIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAaQBjACAARwBy
|
||||
AGEAeQAgAFAAcgBvAGYAaQBsAGUAUABlAHIAZgBpAGwAIABkAGUAIABnAHIAaQBzACAAZwBlAG4A6ABy
|
||||
AGkAYwBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBtACAAQwBoAHUAbgBnAFAAZQByAGYAaQBs
|
||||
ACAAQwBpAG4AegBhACAARwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQw
|
||||
BDkEOwAgAEcAcgBhAHkAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAGcAcgBpAHMAwQBs
|
||||
AHQAYQBsAOEAbgBvAHMAIABzAHoA/AByAGsAZQAgAHAAcgBvAGYAaQBskBp1KHBwlo6Ccl9pY8+P8ABH
|
||||
AGUAbgBlAHIAaQBzAGsAIABnAHIA5QB0AG8AbgBlAHAAcgBvAGYAaQBsx3y8GAAgAEcAcgBhAHkAINUE
|
||||
uFzTDMd8AE8AYgBlAGMAbgD9ACABYQBlAGQA/QAgAHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAARwBy
|
||||
AGEAeQAgBdsF3AXcBdkAUAByAG8AZgBpAGwAIABnAHIAaQAgAGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBl
|
||||
AG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBvAGYAaQBsAFAAcgBvAGYAaQBs
|
||||
AG8AIABnAHIAaQBnAGkAbwAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QBz
|
||||
AGsAYQBsAGUAcAByAG8AZgBpAGxmbpAacHBepmPPj/Blh072TgCCLDCwMOwwpDDXMO0w1TChMKQw6wOT
|
||||
A7UDvQO5A7oDzAAgA8ADwQO/A8YDrwO7ACADswO6A8EDuQBQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBp
|
||||
AGMAbwAgAGQAZQAgAGMAaQBuAHoAZQBuAHQAbwBzAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwBw
|
||||
AHIAbwBmAGkAZQBsAFAAZQByAGYAaQBsACAAZwByAGkAcwAgAGcAZQBuAOkAcgBpAGMAbw5CDhsOIw5E
|
||||
Dh8OJQ5MDioONQ5ADhcOMg4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABHAHIAaQAgAFAAcgBvAGYAaQBs
|
||||
AGkAWQBsAGUAaQBuAGUAbgAgAGgAYQByAG0AYQBhAHAAcgBvAGYAaQBpAGwAaQBHAGUAbgBlAHIAaQEN
|
||||
AGsAaQAgAHAAcgBvAGYAaQBsACAAcwBpAHYAaQBoACAAdABvAG4AbwB2AGEAVQBuAGkAdwBlAHIAcwBh
|
||||
AGwAbgB5ACAAcAByAG8AZgBpAGwAIABzAHoAYQByAG8BWwBjAGkEHgQxBEkEOAQ5ACAEQQQ1BEAESwQ5
|
||||
ACAEPwRABD4ERAQ4BDsETAZFBkQGQQAgBioGOQYxBkoGQQAgAEcAcgBhAHkAIAYnBkQGOQYnBkUARwBl
|
||||
AG4AZQByAGUAbAAgAGcAcgDlAHQAbwBuAGUAYgBlAHMAawByAGkAdgBlAGwAcwBlAAB0ZXh0AAAAAENv
|
||||
cHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUQAB
|
||||
AAAAARbMY3VydgAAAAAAAAABAc0AANIrLC0uWiRjbGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcElt
|
||||
YWdlUmVwoy0vMFpOU0ltYWdlUmVwWE5TT2JqZWN00issMjNXTlNBcnJheaIyMNIrLDU2Xk5TTXV0YWJs
|
||||
ZUFycmF5ozUyMNM4OQ86OzxXTlNXaGl0ZVxOU0NvbG9yU3BhY2VEMCAwABADgAzSKyw+P1dOU0NvbG9y
|
||||
oj4w0issQUJXTlNJbWFnZaJBMAAIABEAGgAkACkAMgA3AEkATABRAFMAYgBoAHUAfACLAJIAnwCmAK4A
|
||||
sACyALQAuQC7AL0AxADJANQA1gDYANoA3wDiAOQA5gDoAO8BBgEiASQBJgnuCfMJ/goHChoKHgopCjIK
|
||||
Nwo/CkIKRwpWCloKYQppCnYKewp9Cn8KhAqMCo8KlAqcAAAAAAAAAgEAAAAAAAAAQwAAAAAAAAAAAAAA
|
||||
AAAACp8
|
||||
</mutableData>
|
||||
</image>
|
||||
</resources>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>0.4.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -33,7 +33,7 @@
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2016-2018 Background Music contributors</string>
|
||||
<string>Copyright © 2016-2024 Background Music contributors</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
@@ -46,5 +46,11 @@
|
||||
</array>
|
||||
<key>OSAScriptingDefinition</key>
|
||||
<string>BGMApp.sdef</string>
|
||||
<!--
|
||||
Hopefully this will keep Background Music from using the discrete GPU on multi-GPU systems,
|
||||
which wastes energy. I don't have any hardware I could easily test this on, so it's untested.
|
||||
-->
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
return (DecibelApplication* __nullable)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMGooglePlayMusicDesktopPlayer.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
// We have a lot more code for GPMDP than most music players largely because GPMDP has a WebSockets
|
||||
// API and because the user has to enter a code from GPMDP to allow BGMApp to control it.
|
||||
// Currently, the other music players all have AppleScript APIs, so for them the OS asks the user
|
||||
// for permission on our behalf automatically and handles the whole process for us.
|
||||
//
|
||||
// This class implements the usual BGMMusicPlayer methods and handles the UI for authenticating
|
||||
// with GPMDP. BGMGooglePlayMusicDesktopPlayerConnection manages the connection to GPMDP and hides
|
||||
// the details of its API.
|
||||
//
|
||||
|
||||
// Superclass/Protocol Import
|
||||
#import "BGMMusicPlayer.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
API_AVAILABLE(macos(10.10))
|
||||
@interface BGMGooglePlayMusicDesktopPlayer : BGMMusicPlayerBase<BGMMusicPlayer>
|
||||
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMGooglePlayMusicDesktopPlayer.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMGooglePlayMusicDesktopPlayer.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Types.h"
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMAppWatcher.h"
|
||||
#import "BGMGooglePlayMusicDesktopPlayerConnection.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CADebugMacros.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@implementation BGMGooglePlayMusicDesktopPlayer {
|
||||
BGMUserDefaults* userDefaults;
|
||||
BGMGooglePlayMusicDesktopPlayerConnection* connection;
|
||||
BGMAppWatcher* appWatcher;
|
||||
|
||||
// True while the auth code dialog is open. The user types in the four-digit auth code from
|
||||
// GPMDP when we connect to it for the first time.
|
||||
BOOL showingAuthCodeDialog;
|
||||
// True if the user has cancelled the auth code dialog. We only show the auth code dialog again
|
||||
// after the user has changed the music player and then changed it back to GPMDP (or restarted
|
||||
// BGMApp).
|
||||
BOOL authCancelled;
|
||||
}
|
||||
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults {
|
||||
return @[[[self alloc] initWithUserDefaults:userDefaults]];
|
||||
}
|
||||
|
||||
- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults {
|
||||
// If you're copying this class, replace the ID string with a new one generated by uuidgen (the
|
||||
// command line tool).
|
||||
NSUUID* playerID = [BGMMusicPlayerBase makeID:@"FCDCC01F-4BF1-4AD2-BE3E-6B7659A90A3F"];
|
||||
if ((self = [super initWithMusicPlayerID:playerID
|
||||
name:@"GPMDP"
|
||||
toolTip:@"Google Play Music Desktop Player"
|
||||
bundleID:@"google-play-music-desktop-player"])) {
|
||||
userDefaults = defaults;
|
||||
showingAuthCodeDialog = NO;
|
||||
authCancelled = NO;
|
||||
|
||||
// We don't strictly need to use a weak ref (at least not yet), but it doesn't hurt.
|
||||
BGMGooglePlayMusicDesktopPlayer* __weak weakSelf = self;
|
||||
|
||||
connection = [[BGMGooglePlayMusicDesktopPlayerConnection alloc]
|
||||
initWithUserDefaults:userDefaults
|
||||
authRequiredHandler:^{
|
||||
BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;
|
||||
return [strongSelf requestAuthCodeFromUser];
|
||||
}
|
||||
connectionErrorHandler:^{
|
||||
BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;
|
||||
[strongSelf showConnectionErrorDialog];
|
||||
}
|
||||
apiVersionMismatchHandler:^(NSString* reportedAPIVersion) {
|
||||
BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;
|
||||
[strongSelf showAPIVersionMismatchDialog:reportedAPIVersion];
|
||||
}];
|
||||
|
||||
// Set up callbacks that run when GPMDP is opened or closed.
|
||||
appWatcher = [[BGMAppWatcher alloc]
|
||||
initWithBundleID:BGMNN(self.bundleID)
|
||||
appLaunched:^{
|
||||
BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;
|
||||
[strongSelf gpmdpWasLaunched];
|
||||
}
|
||||
appTerminated:^{
|
||||
BGMGooglePlayMusicDesktopPlayer* strongSelf = weakSelf;
|
||||
[strongSelf gpmdpWasTerminated];
|
||||
}];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) gpmdpWasLaunched {
|
||||
if (self.selected) {
|
||||
// Reconnect so we can control GPMDP.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::gpmdpWasLaunched: GPMDP launched. Connecting");
|
||||
|
||||
// Try up to 10 times because GPMDP won't start accepting connections until it's finished
|
||||
// starting up.
|
||||
//
|
||||
// TODO: If GPMDP shows an alert before it finishes launching, it doesn't start accepting
|
||||
// connections until the alert is dismissed, which can make this can timeout.
|
||||
// TODO: Is the error dialog still shown if the user closes GPMDP again while we're
|
||||
// retrying? It shouldn't be.
|
||||
[connection connectWithRetries:10];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) gpmdpWasTerminated {
|
||||
if (self.selected) {
|
||||
// Allow the connection to clean up and reset itself.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::gpmdpWasTerminated: GPMDP has been closed.");
|
||||
[connection disconnect];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
|
||||
// Allow the auth code dialog to be shown again if we were hiding it because the user cancelled
|
||||
// it last time.
|
||||
authCancelled = NO;
|
||||
|
||||
if (self.running) {
|
||||
// Only retry once so the error message is shown fairly quickly if we fail to connect.
|
||||
[connection connectWithRetries:1];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) wasDeselected {
|
||||
[super wasDeselected];
|
||||
[connection disconnect];
|
||||
}
|
||||
|
||||
- (NSString* __nullable) requestAuthCodeFromUser {
|
||||
if (showingAuthCodeDialog) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::requestAuthCodeFromUser: "
|
||||
"Already showing the auth code dialog");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (authCancelled) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::requestAuthCodeFromUser: "
|
||||
"Previously cancelled. Doing nothing.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
showingAuthCodeDialog = YES;
|
||||
|
||||
// Ask the user to read the auth code from GPMDP and type it in to BGMApp.
|
||||
NSString* __nullable authCode = [self showAuthCodeDialog];
|
||||
|
||||
showingAuthCodeDialog = NO;
|
||||
|
||||
return authCode;
|
||||
}
|
||||
|
||||
- (NSString* __nullable) showAuthCodeDialog {
|
||||
// When this isn't being called because the user just changed something in BGMApp (e.g. GPMDP
|
||||
// was closed, they selected it in BGMApp for the first time, then opened GPMDP later), we could
|
||||
// use notifications instead of an NSAlert. But it probably wouldn't happen often enough to be
|
||||
// worth the effort.
|
||||
NSAlert* alert = [NSAlert new];
|
||||
alert.messageText = @"Background Music needs permission to control GPMDP.";
|
||||
alert.informativeText = @"It should be displaying a four-digit code for you to enter.";
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
|
||||
// The text field to type the auth code in.
|
||||
// TODO: Can we derive these dimensions from something instead of hardcoding them?
|
||||
NSTextField* input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 50, 24)];
|
||||
[alert setAccessoryView:input];
|
||||
|
||||
// Focus the text field (so the user doesn't have to do it themselves).
|
||||
[alert.window setInitialFirstResponder:input];
|
||||
|
||||
// Bring GMPDP to the front, underneath our NSAlert, so the user can see the auth code.
|
||||
[self showGPMDPBehindAuthCodeDialog];
|
||||
|
||||
NSModalResponse buttonPressed = [alert runModal];
|
||||
|
||||
if (buttonPressed == NSAlertFirstButtonReturn) {
|
||||
// Set input's value to the text entered by the user so we can access it.
|
||||
[input validateEditing];
|
||||
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::showAuthCodeDialog: Got auth code: <private>");
|
||||
return input.stringValue;
|
||||
} else {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::showAuthCodeDialog: "
|
||||
"The user cancelled the auth code dialog");
|
||||
authCancelled = YES;
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) showGPMDPBehindAuthCodeDialog {
|
||||
// Dispatched because if we do this just before showing the auth code dialog, the user's current
|
||||
// active window will be deactivated, the auth code dialog will become the active window and
|
||||
// macOS will act as if the user activated it themselves. To avoid stealing key focus, it won't
|
||||
// activate GPMDP.
|
||||
//
|
||||
// We could pass NSApplicationActivateIgnoringOtherApps to activateWithOptions instead, but then
|
||||
// GPMDP would be activated even if the user really did activate a different application, which
|
||||
// would steal focus from it.
|
||||
//
|
||||
// 250 ms is a reasonable value on my system, but won't always be long enough. When it isn't,
|
||||
// GPMDP won't be activated, but that just means the user will have to do it themselves.
|
||||
const int64_t delay = 250 * NSEC_PER_MSEC;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),
|
||||
dispatch_get_main_queue(),
|
||||
^{
|
||||
// Make GMPDP the frontmost app.
|
||||
NSArray<NSRunningApplication*>* gpmdpApps =
|
||||
[NSRunningApplication
|
||||
runningApplicationsWithBundleIdentifier:BGMNN(self.bundleID)];
|
||||
|
||||
if (gpmdpApps.count > 0) {
|
||||
[gpmdpApps[0] activateWithOptions:0];
|
||||
}
|
||||
|
||||
// Focus the auth code dialog. It will already be in front of GPMDP because
|
||||
// it's modal. Dispatched for the same reason as above.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),
|
||||
dispatch_get_main_queue(),
|
||||
^{
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void) showConnectionErrorDialog {
|
||||
NSString* errorMsg = @"Could not connect to Google Play Music Desktop Player";
|
||||
NSString* troubleshootingMsg =
|
||||
[NSString stringWithFormat:
|
||||
@"Make sure \"Enable JSON API\" and \"Enable Playback API\" are both checked in GPMDP's "
|
||||
"settings, then restart GPMDP.\n\n"
|
||||
"GPMDP should be listening on its default port, 5672.\n\n"
|
||||
"Consider filing a bug report at %s",
|
||||
kBGMIssueTrackerURL];
|
||||
|
||||
[self showErrorDialog:errorMsg troubleshootingMsg:troubleshootingMsg];
|
||||
}
|
||||
|
||||
- (void) showAPIVersionMismatchDialog:(NSString*)reportedAPIVersion {
|
||||
NSString* errorMsg = @"Google Play Music Desktop Player Version Not Supported";
|
||||
NSString* troubleshootingMsg =
|
||||
[NSString stringWithFormat:
|
||||
@"GPMDP reported its API version as \"%@\", which Background Music doesn't support "
|
||||
"yet. Background Music might not be able to control GPMDP properly.\n\n"
|
||||
"Feel free to open an issue about this at %s",
|
||||
reportedAPIVersion,
|
||||
kBGMIssueTrackerURL];
|
||||
|
||||
[self showErrorDialog:errorMsg troubleshootingMsg:troubleshootingMsg];
|
||||
}
|
||||
|
||||
- (void) showErrorDialog:(NSString*)errorMsg troubleshootingMsg:(NSString*)troubleshootingMsg {
|
||||
if (!self.running) {
|
||||
// GPMDP isn't running, so there's no need to inform the user. (The "Auto-pause GPMDP" menu
|
||||
// item will be greyed out, but that's handled elsewhere.)
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::showErrorDialog: Not running");
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"%@", errorMsg);
|
||||
|
||||
// Show the error in a UI dialog.
|
||||
NSAlert* alert = [NSAlert new];
|
||||
alert.messageText = errorMsg;
|
||||
alert.informativeText = troubleshootingMsg;
|
||||
// TODO: Show the suppression checkbox and save its value in user defaults.
|
||||
alert.showsSuppressionButton = NO;
|
||||
[alert addButtonWithTitle:@"OK"];
|
||||
|
||||
[alert runModal];
|
||||
}
|
||||
|
||||
- (BOOL) isRunning {
|
||||
// We have to check with NSRunningApplication instead of just setting a flag in appWatcher's
|
||||
// callbacks because BGMAutoPauseMenuItem calls this method when it's notified by its own
|
||||
// instance of BGMAppWatcher. If BGMAutoPauseMenuItem got notified first, the flag wouldn't be
|
||||
// updated in time.
|
||||
//
|
||||
// At some point we might want to try to avoid this by making the BGMMusicPlayers' running
|
||||
// properties observable.
|
||||
NSArray<NSRunningApplication*>* instances =
|
||||
[NSRunningApplication runningApplicationsWithBundleIdentifier:BGMNN(self.bundleID)];
|
||||
|
||||
return instances.count > 0;
|
||||
}
|
||||
|
||||
- (BOOL) isPlaying {
|
||||
return self.running && connection.playing;
|
||||
}
|
||||
|
||||
- (BOOL) isPaused {
|
||||
return self.running && connection.paused;
|
||||
}
|
||||
|
||||
- (BOOL) pause {
|
||||
// isPlaying checks isRunning, so we don't need to check it here.
|
||||
BOOL wasPlaying = self.playing;
|
||||
|
||||
if (wasPlaying) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::pause: Pausing Google Play Music Desktop "
|
||||
"Player");
|
||||
// There's a race condition here and in unpause because, if the user paused GPMDP just
|
||||
// before we called playPause, GPMDP would play instead of pausing. I'm not sure there's
|
||||
// much we can/should do about it.
|
||||
[connection playPause];
|
||||
}
|
||||
|
||||
return wasPlaying;
|
||||
}
|
||||
|
||||
- (BOOL) unpause {
|
||||
// isPaused checks isRunning, so we don't need to check it here.
|
||||
BOOL wasPaused = self.paused;
|
||||
|
||||
if (wasPaused) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayer::unpause: Unpausing Google Play Music Desktop "
|
||||
"Player");
|
||||
[connection playPause];
|
||||
}
|
||||
|
||||
return wasPaused;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMGooglePlayMusicDesktopPlayerConnection.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#import "BGMUserDefaults.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
API_AVAILABLE(macos(10.10))
|
||||
@interface BGMGooglePlayMusicDesktopPlayerConnection : NSObject<WKScriptMessageHandler>
|
||||
|
||||
// authRequiredHandler: A UI callback that asks the user for the auth code GPMDP will display.
|
||||
// Returns the auth code they entered, or nil.
|
||||
// connectionErrorHandler: A UI callback that shows a connection error message.
|
||||
// apiVersionMismatchHandler: A UI callback that shows a warning dialog explaining that GPMDP
|
||||
// reported an API version that we don't support yet.
|
||||
- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults
|
||||
authRequiredHandler:(NSString* __nullable (^)(void))authHandler
|
||||
connectionErrorHandler:(void (^)(void))errorHandler
|
||||
apiVersionMismatchHandler:(void (^)(NSString* reportedAPIVersion))apiVersionHandler;
|
||||
|
||||
// Returns before the connection has been fully established. The playing and paused properties will
|
||||
// remain false until the connection is complete, but playPause can be called at any time after
|
||||
// calling this method.
|
||||
//
|
||||
// If the connection fails, it will be retried after a one second delay, up to the number of times
|
||||
// given.
|
||||
- (void) connectWithRetries:(int)retries;
|
||||
- (void) disconnect;
|
||||
|
||||
// Tell GPMDP to play if it's paused or pause if it's playing.
|
||||
- (void) playPause;
|
||||
|
||||
@property (readonly) BOOL playing;
|
||||
@property (readonly) BOOL paused;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMGooglePlayMusicDesktopPlayerConnection.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMGooglePlayMusicDesktopPlayerConnection.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CADebugMacros.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
// When GooglePlayMusicDesktopPlayer.js sends a message to this class, it sets the message handler
|
||||
// name to one of these, which tells us what type of message it is. (This is a macro because you
|
||||
// can't make a static const NSArray.)
|
||||
#define kScriptMessageHandlerNames (@[@"gpmdp", @"log", @"error"])
|
||||
|
||||
@implementation BGMGooglePlayMusicDesktopPlayerConnection {
|
||||
// GPMDP has a WebSocket API, so we use a WKWebView to access it using Javascript. Using a
|
||||
// proper library would make the code a bit cleaner and save a little memory, but I'm not sure
|
||||
// it would be worth adding an external dependency for that.
|
||||
WKWebView* webView;
|
||||
NSString* __nullable permanentAuthCode;
|
||||
BGMUserDefaults* userDefaults;
|
||||
// The number of times to retry if we fail to connect. For example, if GPMDP is still starting
|
||||
// up. Set to 0 when we aren't trying to connect.
|
||||
int connectionRetries;
|
||||
|
||||
// A UI callback that asks the user for the auth code GPMDP will display.
|
||||
NSString* __nullable (^authRequiredHandler)(void);
|
||||
// A UI callback that shows a connection error message.
|
||||
void (^connectionErrorHandler)(void);
|
||||
// A UI callback that shows a warning dialog explaining that GPMDP reported an API version that
|
||||
// we don't support yet.
|
||||
void (^apiVersionMismatchHandler)(NSString* reportedAPIVersion);
|
||||
}
|
||||
|
||||
- (instancetype) initWithUserDefaults:(BGMUserDefaults*)defaults
|
||||
authRequiredHandler:(NSString* __nullable (^)(void))authHandler
|
||||
connectionErrorHandler:(void (^)(void))errorHandler
|
||||
apiVersionMismatchHandler:(void (^)(NSString* reportedAPIVersion))apiVersionHandler {
|
||||
if((self = [super init])) {
|
||||
userDefaults = defaults;
|
||||
authRequiredHandler = authHandler;
|
||||
connectionErrorHandler = errorHandler;
|
||||
apiVersionMismatchHandler = apiVersionHandler;
|
||||
connectionRetries = 0;
|
||||
|
||||
// Lazily initialised.
|
||||
permanentAuthCode = nil;
|
||||
|
||||
// Report that GPMDP is stopped until we know otherwise.
|
||||
_playing = NO;
|
||||
_paused = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Creates and initialises webView, a WKWebView we use to communicate with GPMDP over WebSockets.
|
||||
- (void) createWebView {
|
||||
// Read the Javascript we'll need for this.
|
||||
NSString* __nullable jsPath =
|
||||
[[NSBundle mainBundle] pathForResource:@"GooglePlayMusicDesktopPlayer.js"
|
||||
ofType:nil];
|
||||
NSError* err;
|
||||
NSString* __nullable jsStr =
|
||||
(!jsPath ? nil : [NSString stringWithContentsOfFile:BGMNN(jsPath)
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&err]);
|
||||
|
||||
if (err || !jsStr || [jsStr isEqualToString:@""]) {
|
||||
// TODO: Return an error so the caller can show an error dialog or something.
|
||||
NSLog(@"Error loading GPMDP Javascript file: %@", err);
|
||||
} else {
|
||||
webView = [WKWebView new];
|
||||
|
||||
// Register to receive messages from our Javascript. The messages are handled in
|
||||
// userContentController. We register several times using different names as a convenient
|
||||
// way to separate messages from GPMDP, messages to log and errors.
|
||||
for (NSString* name in kScriptMessageHandlerNames) {
|
||||
[webView.configuration.userContentController addScriptMessageHandler:self name:name];
|
||||
}
|
||||
|
||||
// Load our Javascript functions into webView so we can call them later.
|
||||
[self evaluateJavaScript:BGMNN(jsStr)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) connectWithRetries:(int)retries {
|
||||
if (retries < 0) {
|
||||
BGMAssert(false, "retries < 0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!permanentAuthCode) {
|
||||
// Read the API auth code from user defaults (actually the keychain), if there is one. If
|
||||
// the user hasn't authenticated before, it will be nil.
|
||||
//
|
||||
// We do this lazily because it can show a password dialog in debug/unsigned builds.
|
||||
permanentAuthCode = userDefaults.googlePlayMusicDesktopPlayerPermanentAuthCode;
|
||||
}
|
||||
|
||||
connectionRetries = retries;
|
||||
|
||||
// Create the WKWebView we'll use to connect to GPMDP with WebSockets. Using a WKWebView means
|
||||
// Background Music uses a bit more memory while connected to GPMDP, around 15 MB for me, but
|
||||
// saves us having to complicate the build process to add a dependency on a proper library.
|
||||
[self createWebView];
|
||||
|
||||
if (permanentAuthCode) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::connectWithRetries: "
|
||||
"Connecting with auth code");
|
||||
|
||||
NSString* __nullable percentEncodedCode =
|
||||
[BGMGooglePlayMusicDesktopPlayerConnection
|
||||
toPercentEncoded:BGMNN(permanentAuthCode)];
|
||||
|
||||
[self evaluateJavaScript:[NSString stringWithFormat:@"connect('%@');", percentEncodedCode]];
|
||||
} else {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::connectWithRetries: "
|
||||
"Connecting without auth code");
|
||||
[self evaluateJavaScript:@"connect();"];
|
||||
}
|
||||
|
||||
// Check whether GPMDP is playing, paused or stopped.
|
||||
[self requestPlaybackState];
|
||||
}
|
||||
|
||||
- (void) disconnect {
|
||||
// Stop retrying if we're in the process of connecting.
|
||||
connectionRetries = 0;
|
||||
|
||||
// evaluateJavaScript is only safe to call on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::disconnect: Disconnecting");
|
||||
|
||||
[webView evaluateJavaScript:@"disconnect();"
|
||||
completionHandler:^(id __nullable result, NSError* __nullable error) {
|
||||
#pragma unused (result)
|
||||
if (error) {
|
||||
NSLog(@"Error closing connection to GPMDP: %@", error);
|
||||
}
|
||||
|
||||
// Allow the WKWebView to be garbage collected.
|
||||
for (NSString* name in kScriptMessageHandlerNames) {
|
||||
[webView.configuration.userContentController
|
||||
removeScriptMessageHandlerForName:name];
|
||||
}
|
||||
webView = nil;
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) evaluateJavaScript:(NSString*)js {
|
||||
// evaluateJavaScript is only safe to call on the main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webView evaluateJavaScript:js
|
||||
completionHandler:^(id __nullable result, NSError* __nullable error) {
|
||||
#pragma unused (result)
|
||||
if (error) {
|
||||
// TODO: We should probably show an error dialog in some cases.
|
||||
NSLog(@"JS error: %@", error);
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) playPause {
|
||||
[self evaluateJavaScript:@"playPause();"];
|
||||
}
|
||||
|
||||
- (void) sendAuthCode:(NSString*)authCode {
|
||||
// Don't log the code itself just in case it could be a security problem.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::sendAuthCode: Sending GPMDP auth code");
|
||||
|
||||
// Percent-encode the user input just in case they entered something that could execute as
|
||||
// Javascript. We could limit the input to four digits instead, but this should be fine.
|
||||
NSString* __nullable percentEncodedCode =
|
||||
[BGMGooglePlayMusicDesktopPlayerConnection toPercentEncoded:authCode];
|
||||
|
||||
// We send the message to GPMDP even if percentEncodedCode is nil so it will reply with an error
|
||||
// and BGMApp will ask the user for the auth code again.
|
||||
NSString* js = [NSString stringWithFormat:@"window.sendAuthCode('%@');", percentEncodedCode];
|
||||
[self evaluateJavaScript:js];
|
||||
}
|
||||
|
||||
- (void) sendPermanentAuthCode {
|
||||
NSString* __nullable code = permanentAuthCode;
|
||||
|
||||
if (code) {
|
||||
// Don't log the code itself just in case it could be a security problem.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::sendPermanentAuthCode: "
|
||||
"Sending GPMDP permanent auth code");
|
||||
|
||||
// Percent-encode it just in case something it includes could be executed as Javascript.
|
||||
NSString* __nullable percentEncodedCode =
|
||||
[BGMGooglePlayMusicDesktopPlayerConnection toPercentEncoded:BGMNN(code)];
|
||||
|
||||
// Pass the code to our WKWebView so it can send it to GPMDP.
|
||||
NSString* js =
|
||||
[NSString stringWithFormat:@"sendPermanentAuthCode('%@');", percentEncodedCode];
|
||||
[self evaluateJavaScript:js];
|
||||
} else {
|
||||
NSLog(@"BGMGooglePlayMusicDesktopPlayerConnection::sendPermanentAuthCode: No code to send");
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString* __nullable)toPercentEncoded:(NSString*)rawString {
|
||||
// Just percent-encode every character (by passing an empty NSCharacterSet as the allowed
|
||||
// characters).
|
||||
NSString* __nullable percentEncoded = [rawString
|
||||
stringByAddingPercentEncodingWithAllowedCharacters:
|
||||
[NSCharacterSet characterSetWithCharactersInString:@""]];
|
||||
if (percentEncoded) {
|
||||
return percentEncoded;
|
||||
} else {
|
||||
// The docs say that stringByAddingPercentEncodingWithAllowedCharacters returns nil "if the
|
||||
// transformation is not possible", but don't explain when that could happen. According to
|
||||
// https://stackoverflow.com/a/33558934/1091063 it can be caused by the string containing
|
||||
// invalid unicode.
|
||||
NSLog(@"Could not encode");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Ask GPMDP whether it's playing, paused or stopped. The response is handled asynchronously in
|
||||
// handleResultMessage.
|
||||
- (void) requestPlaybackState {
|
||||
[self evaluateJavaScript:@"requestPlaybackState();"];
|
||||
}
|
||||
|
||||
#pragma mark WKScriptMessageHandler Methods
|
||||
|
||||
- (void) userContentController:(WKUserContentController*)userContentController
|
||||
didReceiveScriptMessage:(WKScriptMessage*)message {
|
||||
#pragma unused (userContentController)
|
||||
|
||||
if ([@"log" isEqual:message.name]) {
|
||||
// The message body is always a string in this case.
|
||||
[self handleLogMessage:message.body];
|
||||
} else if ([@"error" isEqual:message.name]) {
|
||||
[self handleConnectionError];
|
||||
} else {
|
||||
BGMAssert([@"gpmdp" isEqual:message.name], "Unexpected message handler name");
|
||||
[self handleGPMDPMessage:message];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) handleLogMessage:(NSString*)message {
|
||||
(void)message;
|
||||
#if DEBUG
|
||||
if (permanentAuthCode) {
|
||||
// Avoid logging the auth code, which would be a minor security issue.
|
||||
message = [message stringByReplacingOccurrencesOfString:BGMNN(permanentAuthCode)
|
||||
withString:@"<private>"];
|
||||
}
|
||||
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::userContentController: %s",
|
||||
message.UTF8String);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) handleConnectionError {
|
||||
if (connectionRetries > 0) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleConnectionError: "
|
||||
"Retrying in 1 second");
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(),
|
||||
^{
|
||||
// Check connectionRetries again because disconnect may have been called.
|
||||
if (connectionRetries > 0) {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::"
|
||||
"handleConnectionError: Retrying");
|
||||
[self connectWithRetries:(connectionRetries - 1)];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NSLog(@"BGMGooglePlayMusicDesktopPlayerConnection::handleConnectionError: "
|
||||
"No retries left. Giving up.");
|
||||
connectionErrorHandler();
|
||||
}
|
||||
}
|
||||
|
||||
- (void) handleGPMDPMessage:(WKScriptMessage*)message {
|
||||
// See https://github.com/MarshallOfSound/Google-Play-Music-Desktop-Player-UNOFFICIAL-/blob/master/docs/PlaybackAPI_WebSocket.md
|
||||
|
||||
// Type check.
|
||||
if (![message.body isKindOfClass:[NSDictionary class]]) {
|
||||
NSLog(@"Unexpected message body type");
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary* body = message.body;
|
||||
NSString* messageType;
|
||||
|
||||
// The key for the message type is "channel", except when the message is a response, in which
|
||||
// case the key can be "namespace".
|
||||
if ([body[@"channel"] isKindOfClass:[NSString class]]) {
|
||||
messageType = body[@"channel"];
|
||||
} else if ([body[@"namespace"] isKindOfClass:[NSString class]]) {
|
||||
messageType = body[@"namespace"];
|
||||
} else {
|
||||
NSLog(@"No channel/namespace");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle the message depending on its type (or ignore it).
|
||||
if ([@"API_VERSION" isEqual:messageType]) {
|
||||
[self handleAPIVersionMessage:body];
|
||||
} else if ([@"connect" isEqual:messageType]) {
|
||||
[self handleConnectMessage:body];
|
||||
} else if ([@"playState" isEqual:messageType]) {
|
||||
[self handlePlayStateMessage:body];
|
||||
} else if ([@"result" isEqual:messageType]) {
|
||||
[self handleResultMessage:body];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) handleAPIVersionMessage:(NSDictionary*)body {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: Response: %s",
|
||||
[NSString stringWithFormat:@"%@", body].UTF8String);
|
||||
|
||||
// Type check.
|
||||
if (![body[@"payload"] isKindOfClass:[NSString class]]) {
|
||||
NSLog(@"Unexpected payload type");
|
||||
[self handleConnectionError];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* apiVersion = body[@"payload"];
|
||||
// "1.0.0" -> ["1", "0", "0"]
|
||||
NSArray<NSString*>* versionParts = [apiVersion componentsSeparatedByString:@"."];
|
||||
|
||||
// Check the major version number is 1, which is the only major version we support.
|
||||
if (versionParts.count > 0) {
|
||||
NSInteger majorVersion = versionParts[0].integerValue;
|
||||
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: "
|
||||
"Major version: %lu", majorVersion);
|
||||
|
||||
if (majorVersion == 1) {
|
||||
// GPMDP uses SemVer, so as long as the major version number matches what we can handle,
|
||||
// it should work.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: "
|
||||
"This API version is supported");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Show a warning dialog box to the user, but try to continue anyway. There's probably a
|
||||
// reasonable chance it'll still work.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleAPIVersionMessage: "
|
||||
"Unsupported GPMDP API version");
|
||||
apiVersionMismatchHandler(apiVersion);
|
||||
}
|
||||
|
||||
- (void) handleConnectMessage:(NSDictionary*)body {
|
||||
// Don't log the response as it may contain the auth code.
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleConnectMessage: Received response");
|
||||
|
||||
// Type check.
|
||||
if (![body[@"payload"] isKindOfClass:[NSString class]]) {
|
||||
NSLog(@"Unexpected payload type");
|
||||
[self handleConnectionError];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* payload = body[@"payload"];
|
||||
|
||||
if ([@"CODE_REQUIRED" isEqual:payload]) {
|
||||
// Ask the user for the auth code GPMDP is displaying and send it to GPMDP to finish
|
||||
// connecting.
|
||||
NSString* __nullable authCode = authRequiredHandler();
|
||||
|
||||
if (authCode) {
|
||||
[self sendAuthCode:BGMNN(authCode)];
|
||||
}
|
||||
} else {
|
||||
// The payload should be the permanent auth code.
|
||||
permanentAuthCode = payload;
|
||||
[self sendPermanentAuthCode];
|
||||
|
||||
// Save the code to the keychain so we can use it when connecting to GPMDP in future.
|
||||
userDefaults.googlePlayMusicDesktopPlayerPermanentAuthCode = permanentAuthCode;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) handlePlayStateMessage:(NSDictionary*)body {
|
||||
(void)body;
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handlePlayStateMessage: Response: %s",
|
||||
[NSString stringWithFormat:@"%@", body].UTF8String);
|
||||
|
||||
// This message tells us the playstate has changed, but doesn't differentiate between stopped
|
||||
// and paused. The response to this API request will. See handleResultMessage.
|
||||
// TODO: Can it transition from stopped to paused? Would that be a problem?
|
||||
[self requestPlaybackState];
|
||||
}
|
||||
|
||||
- (void) handleResultMessage:(NSDictionary*)body {
|
||||
DebugMsg("BGMGooglePlayMusicDesktopPlayerConnection::handleResultMessage: Response: %s",
|
||||
[NSString stringWithFormat:@"%@", body].UTF8String);
|
||||
|
||||
// Type check.
|
||||
if (![body[@"value"] isKindOfClass:[NSNumber class]]) {
|
||||
NSLog(@"No value");
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 - Playback is stopped
|
||||
// 1 - Track is paused
|
||||
// 2 - Track is playing
|
||||
int state = ((NSNumber*)body[@"value"]).intValue;
|
||||
_playing = (state == 2);
|
||||
_paused = (state == 1);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
return (HermesApplication* __nullable)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMMusic.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2019 Kyle Neideck
|
||||
// Copyright © 2019 theLMGN
|
||||
//
|
||||
|
||||
// Superclass/Protocol Import
|
||||
#import "BGMMusicPlayer.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@interface BGMMusic : BGMMusicPlayerBase<BGMMusicPlayer>
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMMusic.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2019 Kyle Neideck, theLMGN
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMMusic.h"
|
||||
|
||||
// Auto-generated Scripting Bridge header
|
||||
#import "Music.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGMScriptingBridge.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CADebugMacros.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@implementation BGMMusic {
|
||||
BGMScriptingBridge* scriptingBridge;
|
||||
}
|
||||
|
||||
+ (NSUUID*) sharedMusicPlayerID {
|
||||
NSUUID* __nullable musicPlayerID =
|
||||
[[NSUUID alloc] initWithUUIDString:@"829B8069-8BD2-481D-BD40-54AB8CDAE228"];
|
||||
NSAssert(musicPlayerID, @"BGMMusic::sharedMusicPlayerID: !musicPlayerID");
|
||||
return (NSUUID*)musicPlayerID;
|
||||
}
|
||||
|
||||
- (instancetype) init {
|
||||
if ((self = [super initWithMusicPlayerID:[BGMMusic sharedMusicPlayerID]
|
||||
name:@"Music"
|
||||
bundleID:@"com.apple.Music"])) {
|
||||
scriptingBridge = [[BGMScriptingBridge alloc] initWithMusicPlayer:self];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MusicApplication* __nullable) music {
|
||||
return (MusicApplication*)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
- (BOOL) isRunning {
|
||||
return self.music.running;
|
||||
}
|
||||
|
||||
// isPlaying and isPaused check self.running first just in case Music is closed but self.music
|
||||
// hasn't become nil yet. In that case, reading self.music.playerState could make Scripting Bridge
|
||||
// open Music.
|
||||
|
||||
- (BOOL) isPlaying {
|
||||
return self.running && (self.music.playerState == MusicEPlSPlaying);
|
||||
}
|
||||
|
||||
- (BOOL) isPaused {
|
||||
return self.running && (self.music.playerState == MusicEPlSPaused);
|
||||
}
|
||||
|
||||
- (BOOL) pause {
|
||||
// isPlaying checks isRunning, so we don't need to check it here and waste an Apple event
|
||||
BOOL wasPlaying = self.playing;
|
||||
|
||||
if (wasPlaying) {
|
||||
DebugMsg("BGMMusic::pause: Pausing Music");
|
||||
[self.music 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.paused;
|
||||
|
||||
if (wasPaused) {
|
||||
DebugMsg("BGMMusic::unpause: Unpausing Music");
|
||||
[self.music playpause];
|
||||
}
|
||||
|
||||
return wasPaused;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMMusicPlayer.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016, 2018 Kyle Neideck
|
||||
// Copyright © 2016, 2018, 2019 Kyle Neideck
|
||||
//
|
||||
// The base classes and protocol for objects that represent a music player app.
|
||||
//
|
||||
@@ -41,6 +41,9 @@
|
||||
// BGMDriver will log the bundle ID to system.log when it becomes aware of the music player.
|
||||
//
|
||||
|
||||
// Local Includes
|
||||
#import "BGMUserDefaults.h"
|
||||
|
||||
// System Includes
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@@ -50,27 +53,26 @@
|
||||
@protocol BGMMusicPlayer <NSObject>
|
||||
|
||||
// Classes return an instance of themselves for each music player app they make available in
|
||||
// BGMApp. So far that's always been a single instance, and classes haven't needed to override
|
||||
// the default implementation of createInstances from BGMMusicPlayerBase. But that will probably
|
||||
// change eventually.
|
||||
// BGMApp. So far that's always been a single instance, but that will probably change eventually.
|
||||
// Most classes don't need to override the default implementation from BGMMusicPlayerBase.
|
||||
//
|
||||
// For example, a class for custom music players would probably return an instance for each
|
||||
// custom player the user has created. (Also note that it could return an empty array.) In that
|
||||
// case the class would probably restore some state from user defaults in its createInstances.
|
||||
// But, for example, a class for custom music players would probably return an instance for each
|
||||
// custom player the user has created. (Also note that it could return an empty array.)
|
||||
//
|
||||
// TODO: I think the return type should actually be NSArray<instancetype>*, but that doesn't seem
|
||||
// to work. There's a Clang bug about this: https://llvm.org/bugs/show_bug.cgi?id=27323
|
||||
// (though it hasn't been confirmed yet).
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstances;
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;
|
||||
|
||||
// We need a unique ID for each music player to store in user defaults. In the most common case,
|
||||
// classes that provide a static (or at least bounded) number of music players, you can generate
|
||||
// IDs with uuidgen (the command line tool) and include them in your class as constants. Otherwise,
|
||||
// you'll probably want to store them in user defaults and retrieve them in your createInstances.
|
||||
// you'll probably want to store them in user defaults and load them in createInstancesWithDefaults.
|
||||
@property (readonly) NSUUID* musicPlayerID;
|
||||
|
||||
// The name and icon of the music player, to be used in the UI.
|
||||
// The name, tool-tip and icon of the music player, to be used in the UI.
|
||||
@property (readonly) NSString* name;
|
||||
@property (readonly) NSString* __nullable toolTip;
|
||||
@property (readonly) NSImage* __nullable icon;
|
||||
|
||||
@property (readonly) NSString* __nullable bundleID;
|
||||
@@ -81,7 +83,7 @@
|
||||
// TODO: If we ever add a music player class that uses this property, it'll need a way to inform
|
||||
// BGMDevice of changes. It might be easiest to have BGMMusicPlayers to observe this property,
|
||||
// on the selected music player, with KVO and update BGMDevice when it changes. Or
|
||||
// BGMMusicPlayers could pass a pointer to itself to createInstances.
|
||||
// BGMMusicPlayers could pass a pointer to itself to createInstancesWithDefaults.
|
||||
@property NSNumber* __nullable pid;
|
||||
|
||||
// True if this is currently the selected music player.
|
||||
@@ -101,9 +103,9 @@
|
||||
@property (readonly, getter=isPaused) BOOL paused;
|
||||
|
||||
// Called when the user selects this music player.
|
||||
- (void) onSelect;
|
||||
// Called when this is the selected music player and the user selects a different one.
|
||||
- (void) onDeselect;
|
||||
- (void) wasSelected;
|
||||
// Called when this was the selected music player and the user just selected a different one.
|
||||
- (void) wasDeselected;
|
||||
|
||||
// Pause the music player. Does nothing if the music player is already paused or isn't running.
|
||||
// Returns YES if the music player is paused now but wasn't before, returns NO otherwise.
|
||||
@@ -123,6 +125,12 @@
|
||||
|
||||
- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID
|
||||
name:(NSString*)name
|
||||
toolTip:(NSString*)toolTip
|
||||
bundleID:(NSString* __nullable)bundleID;
|
||||
|
||||
- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID
|
||||
name:(NSString*)name
|
||||
toolTip:(NSString* __nullable)toolTip
|
||||
bundleID:(NSString* __nullable)bundleID
|
||||
pid:(NSNumber* __nullable)pid;
|
||||
|
||||
@@ -131,15 +139,16 @@
|
||||
+ (NSUUID*) makeID:(NSString*)musicPlayerIDString;
|
||||
|
||||
// BGMMusicPlayer default implementations
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstances;
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults;
|
||||
@property (readonly) NSImage* __nullable icon;
|
||||
@property (readonly) NSUUID* musicPlayerID;
|
||||
@property (readonly) NSString* name;
|
||||
@property (readonly) NSString* __nullable toolTip;
|
||||
@property (readonly) NSString* __nullable bundleID;
|
||||
@property NSNumber* __nullable pid;
|
||||
@property (readonly) BOOL selected;
|
||||
- (void) onSelect;
|
||||
- (void) onDeselect;
|
||||
- (void) wasSelected;
|
||||
- (void) wasDeselected;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMMusicPlayer.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
@synthesize musicPlayerID = _musicPlayerID;
|
||||
@synthesize name = _name;
|
||||
@synthesize toolTip = _toolTip;
|
||||
@synthesize bundleID = _bundleID;
|
||||
@synthesize pid = _pid;
|
||||
@synthesize selected = _selected;
|
||||
@@ -40,11 +41,27 @@
|
||||
- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID
|
||||
name:(NSString*)name
|
||||
bundleID:(NSString* __nullable)bundleID {
|
||||
return [self initWithMusicPlayerID:musicPlayerID name:name bundleID:bundleID pid:nil];
|
||||
return [self initWithMusicPlayerID:musicPlayerID
|
||||
name:name
|
||||
toolTip:nil
|
||||
bundleID:bundleID
|
||||
pid:nil];
|
||||
}
|
||||
|
||||
- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID
|
||||
name:(NSString*)name
|
||||
toolTip:(NSString*)toolTip
|
||||
bundleID:(NSString* __nullable)bundleID {
|
||||
return [self initWithMusicPlayerID:musicPlayerID
|
||||
name:name
|
||||
toolTip:toolTip
|
||||
bundleID:bundleID
|
||||
pid:nil];
|
||||
}
|
||||
|
||||
- (instancetype) initWithMusicPlayerID:(NSUUID*)musicPlayerID
|
||||
name:(NSString*)name
|
||||
toolTip:(NSString* __nullable)toolTip
|
||||
bundleID:(NSString* __nullable)bundleID
|
||||
pid:(NSNumber* __nullable)pid {
|
||||
if ((self = [super init])) {
|
||||
@@ -55,6 +72,7 @@
|
||||
|
||||
_musicPlayerID = musicPlayerID;
|
||||
_name = name;
|
||||
_toolTip = toolTip;
|
||||
_bundleID = bundleID;
|
||||
_pid = pid;
|
||||
_selected = NO;
|
||||
@@ -72,8 +90,13 @@
|
||||
|
||||
#pragma mark BGMMusicPlayer default implementations
|
||||
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstances {
|
||||
+ (NSArray<id<BGMMusicPlayer>>*) createInstancesWithDefaults:(BGMUserDefaults*)userDefaults {
|
||||
#pragma unused (userDefaults)
|
||||
// TODO: Fix this warning properly.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
return @[ [self new] ];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
- (NSImage* __nullable) icon {
|
||||
@@ -84,11 +107,11 @@
|
||||
return (!bundlePath ? nil : [[NSWorkspace sharedWorkspace] iconForFile:(NSString*)bundlePath]);
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
- (void) wasSelected {
|
||||
_selected = YES;
|
||||
}
|
||||
|
||||
- (void) onDeselect {
|
||||
- (void) wasDeselected {
|
||||
_selected = NO;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMMusicPlayers.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2019 Kyle Neideck
|
||||
//
|
||||
// Holds the music players (i.e. BGMMusicPlayer objects) available in BGMApp. Also keeps track of
|
||||
// which music player is currently selected by the user.
|
||||
@@ -43,8 +43,8 @@
|
||||
// defaultMusicPlayerID is the musicPlayerID (see BGMMusicPlayer.h) of the music player that should be
|
||||
// selected by default.
|
||||
//
|
||||
// The createInstances method of each class in musicPlayerClasses will be called, and the results stored
|
||||
// in the musicPlayers property.
|
||||
// The createInstancesWithDefaults method of each class in musicPlayerClasses will be called and
|
||||
// the results will be stored in the musicPlayers property.
|
||||
- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices
|
||||
defaultMusicPlayerID:(NSUUID*)defaultMusicPlayerID
|
||||
musicPlayerClasses:(NSArray<Class<BGMMusicPlayer>>*)musicPlayerClasses
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMMusicPlayers.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self include
|
||||
@@ -34,6 +34,8 @@
|
||||
#import "BGMDecibel.h"
|
||||
#import "BGMHermes.h"
|
||||
#import "BGMSwinsian.h"
|
||||
#import "BGMMusic.h"
|
||||
#import "BGMGooglePlayMusicDesktopPlayer.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
@@ -47,16 +49,25 @@
|
||||
|
||||
- (instancetype) initWithAudioDevices:(BGMAudioDeviceManager*)devices
|
||||
userDefaults:(BGMUserDefaults*)defaults {
|
||||
// The classes handling each music player we support. If you write a new music player class, add
|
||||
// it to this array.
|
||||
NSArray<Class<BGMMusicPlayer>>* mpClasses = @[ [BGMVOX class],
|
||||
[BGMVLC class],
|
||||
[BGMSpotify class],
|
||||
[BGMiTunes class],
|
||||
[BGMDecibel class],
|
||||
[BGMHermes class],
|
||||
[BGMSwinsian class],
|
||||
[BGMMusic class] ];
|
||||
|
||||
// We only support Google Play Music Desktop Player on macOS 10.10 and higher.
|
||||
if (@available(macOS 10.10, *)) {
|
||||
mpClasses = [mpClasses arrayByAddingObject:[BGMGooglePlayMusicDesktopPlayer class]];
|
||||
}
|
||||
|
||||
return [self initWithAudioDevices:devices
|
||||
defaultMusicPlayerID:[BGMiTunes sharedMusicPlayerID]
|
||||
// If you write a new music player class, add it to this array.
|
||||
musicPlayerClasses:@[ [BGMVOX class],
|
||||
[BGMVLC class],
|
||||
[BGMSpotify class],
|
||||
[BGMiTunes class],
|
||||
[BGMDecibel class],
|
||||
[BGMHermes class],
|
||||
[BGMSwinsian class] ]
|
||||
musicPlayerClasses:mpClasses
|
||||
userDefaults:defaults];
|
||||
}
|
||||
|
||||
@@ -70,11 +81,14 @@
|
||||
|
||||
// Init _musicPlayers, an array containing one object for each music player in BGMApp.
|
||||
//
|
||||
// Each music player class has a factory method, createInstances, that returns all the instances of that
|
||||
// class BGMApp will use. (Though so far it's always just one instance.)
|
||||
// Each music player class has a factory method, createInstancesWithDefaults, that returns
|
||||
// all the instances of that class BGMApp will use. (Though so far it's always just one
|
||||
// instance.)
|
||||
NSMutableArray* musicPlayers = [NSMutableArray new];
|
||||
for (Class<BGMMusicPlayer> musicPlayerClass in musicPlayerClasses) {
|
||||
[musicPlayers addObjectsFromArray:[musicPlayerClass createInstances]];
|
||||
NSArray<id<BGMMusicPlayer>>* instances =
|
||||
[musicPlayerClass createInstancesWithDefaults:userDefaults];
|
||||
[musicPlayers addObjectsFromArray:instances];
|
||||
}
|
||||
|
||||
_musicPlayers = [NSArray arrayWithArray:musicPlayers];
|
||||
@@ -132,7 +146,7 @@
|
||||
- (void) initSelectedMusicPlayerFromBGMDevice {
|
||||
// When the selected music player setting hasn't been stored in user defaults yet, we get the music player
|
||||
// bundle ID from the driver and look for the music player with that bundle ID. This is mainly done for
|
||||
// backwards compatability.
|
||||
// backwards compatibility.
|
||||
|
||||
NSString* __nullable bundleID =
|
||||
(__bridge_transfer NSString* __nullable)[audioDevices bgmDevice].GetMusicPlayerBundleID();
|
||||
@@ -215,7 +229,7 @@
|
||||
}
|
||||
|
||||
// Tell the current music player (object) a different player has been selected.
|
||||
[_selectedMusicPlayer onDeselect];
|
||||
[_selectedMusicPlayer wasDeselected];
|
||||
|
||||
_selectedMusicPlayer = newSelectedMusicPlayer;
|
||||
|
||||
@@ -229,7 +243,7 @@
|
||||
userDefaults.selectedMusicPlayerID = _selectedMusicPlayer.musicPlayerID.UUIDString;
|
||||
|
||||
// Tell the music player (object) it's been selected.
|
||||
[_selectedMusicPlayer onSelect];
|
||||
[_selectedMusicPlayer wasSelected];
|
||||
}
|
||||
|
||||
- (void) updateBGMDeviceMusicPlayerProperties {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMScriptingBridge.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Utils.h"
|
||||
#import "BGMAppWatcher.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CADebugMacros.h"
|
||||
@@ -34,8 +35,7 @@
|
||||
|
||||
@implementation BGMScriptingBridge {
|
||||
id<BGMMusicPlayer> __weak _musicPlayer;
|
||||
// Tokens for the notification observers. We need these to remove the observers in dealloc.
|
||||
id _didLaunchToken, _didTerminateToken;
|
||||
BGMAppWatcher* appWatcher;
|
||||
}
|
||||
|
||||
@synthesize application = _application;
|
||||
@@ -57,17 +57,14 @@
|
||||
BGMScriptingBridge* __weak weakSelf = self;
|
||||
|
||||
void (^createSBApplication)(void) = ^{
|
||||
BGMScriptingBridge* __strong strongSelf = weakSelf;
|
||||
BGMScriptingBridge* strongSelf = weakSelf;
|
||||
strongSelf->_application = [SBApplication applicationWithBundleIdentifier:bundleID];
|
||||
// TODO: I think the SBApplication will still keep a strong ref to this object, so we might
|
||||
// have to make a separate delegate object.
|
||||
// TODO: The SBApplication will still keep a strong ref to this object, so we would have to
|
||||
// make a separate delegate object to avoid the retain cycle. Not currently a problem
|
||||
// because we only ever create instances that live forever.
|
||||
strongSelf->_application.delegate = strongSelf;
|
||||
};
|
||||
|
||||
BOOL (^isAboutThisMusicPlayer)(NSNotification*) = ^(NSNotification* note) {
|
||||
return [[note.userInfo[NSWorkspaceApplicationKey] bundleIdentifier] isEqualToString:bundleID];
|
||||
};
|
||||
|
||||
// Add observers that create/destroy the SBApplication when the music player is launched/terminated. We
|
||||
// only create the SBApplication when the music player is open. If it isn't open, creating the
|
||||
// SBApplication or sending it events could launch the music player. Whether or not it does depends on
|
||||
@@ -76,31 +73,20 @@
|
||||
// From the docs for SBApplication's applicationWithBundleIdentifier method:
|
||||
// "For applications that declare themselves to have a dynamic scripting interface, this method will
|
||||
// launch the application if it is not already running."
|
||||
NSNotificationCenter* center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
_didLaunchToken = [center addObserverForName:NSWorkspaceDidLaunchApplicationNotification
|
||||
object:nil
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification* note)
|
||||
{
|
||||
if (isAboutThisMusicPlayer(note)) {
|
||||
DebugMsg("BGMScriptingBridge::initApplication: %s launched",
|
||||
bundleID.UTF8String);
|
||||
createSBApplication();
|
||||
[weakSelf ensurePermission];
|
||||
}
|
||||
}];
|
||||
_didTerminateToken = [center addObserverForName:NSWorkspaceDidTerminateApplicationNotification
|
||||
object:nil
|
||||
queue:nil
|
||||
usingBlock:^(NSNotification* note)
|
||||
{
|
||||
if (isAboutThisMusicPlayer(note)) {
|
||||
DebugMsg("BGMScriptingBridge::initApplication: %s terminated",
|
||||
bundleID.UTF8String);
|
||||
BGMScriptingBridge* __strong strongSelf = weakSelf;
|
||||
strongSelf->_application = nil;
|
||||
}
|
||||
}];
|
||||
appWatcher =
|
||||
[[BGMAppWatcher alloc] initWithBundleID:bundleID
|
||||
appLaunched:^{
|
||||
DebugMsg("BGMScriptingBridge::initApplication: %s launched",
|
||||
bundleID.UTF8String);
|
||||
createSBApplication();
|
||||
[weakSelf ensurePermission];
|
||||
}
|
||||
appTerminated:^{
|
||||
BGMScriptingBridge* strongSelf = weakSelf;
|
||||
DebugMsg("BGMScriptingBridge::initApplication: %s terminated",
|
||||
bundleID.UTF8String);
|
||||
strongSelf->_application = nil;
|
||||
}];
|
||||
|
||||
// Create the SBApplication if the music player is already running.
|
||||
if ([NSRunningApplication runningApplicationsWithBundleIdentifier:bundleID].count > 0) {
|
||||
@@ -108,19 +94,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
// Remove the application launch/termination observers.
|
||||
NSNotificationCenter* center = [NSWorkspace sharedWorkspace].notificationCenter;
|
||||
|
||||
if (_didLaunchToken) {
|
||||
[center removeObserver:_didLaunchToken];
|
||||
}
|
||||
|
||||
if (_didTerminateToken) {
|
||||
[center removeObserver:_didTerminateToken];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) ensurePermission {
|
||||
// Skip this check if running on a version of macOS before 10.14. In that case, we don't require
|
||||
// user permission to send Apple Events. Also skip it if compiling on an earlier version.
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
return (SpotifyApplication* __nullable)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
return (SwinsianApplication* __nullable)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Portions copyright (C) 2012 Peter Ljunglöf. All rights reserved.
|
||||
// Copyright (C) 2012 Peter Ljunglöf. All rights reserved.
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -54,8 +54,8 @@
|
||||
return (VLCApplication*)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
return (VoxApplication*)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
return (iTunesApplication*)scriptingBridge.application;
|
||||
}
|
||||
|
||||
- (void) onSelect {
|
||||
[super onSelect];
|
||||
- (void) wasSelected {
|
||||
[super wasSelected];
|
||||
[scriptingBridge ensurePermission];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// GooglePlayMusicDesktopPlayer.js
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// The specification for GPMDP's API:
|
||||
// https://github.com/MarshallOfSound/Google-Play-Music-Desktop-Player-UNOFFICIAL-/blob/master/docs/PlaybackAPI_WebSocket.md
|
||||
|
||||
try {
|
||||
|
||||
window._log = msg => {
|
||||
window.webkit.messageHandlers.log.postMessage(msg);
|
||||
};
|
||||
|
||||
// Global JS error handler.
|
||||
window.onerror = (msg, url, line, col, error) => {
|
||||
let extra = !col ? '' : '\nColumn: ' + col;
|
||||
extra += !error ? '' : '\nError: ' + error;
|
||||
|
||||
// TODO: I'm not sure this log message is ever actually useful.
|
||||
window._log('Error: ' + msg + '\nURL: ' + url + '\nLine: ' + line + extra);
|
||||
|
||||
window.webkit.messageHandlers.error.postMessage(error);
|
||||
};
|
||||
|
||||
// Send a JSON message to GPMDP.
|
||||
//
|
||||
// If we're connecting, this function will return immediately and the message will be sent after we
|
||||
// finish connecting. Logs an error and returns if window.connect() hasn't been called yet.
|
||||
window._sendJSON = json => {
|
||||
if (window._wsPromise) {
|
||||
window._wsPromise.then(() => {
|
||||
window._sendJSONImmediate(json);
|
||||
}).catch(error => {
|
||||
// TODO: Is there anything else we can do? Retries?
|
||||
window._log('Error sending JSON: ' + JSON.stringify(error));
|
||||
});
|
||||
} else {
|
||||
window._log('Error: No WebSocket promise. Discarding JSON message: ' +
|
||||
JSON.stringify(json));
|
||||
}
|
||||
};
|
||||
|
||||
// Send a JSON message to GPMDP, but don't wait if we're in the process of connecting.
|
||||
//
|
||||
// Logs an error and returns if window.connect() hasn't been called yet. The authCode param is
|
||||
// optional and only used to hide the code in log messages.
|
||||
window._sendJSONImmediate = (json, authCode) => {
|
||||
let jsonStr = JSON.stringify(json);
|
||||
let jsonStrSanitized = authCode ? jsonStr.replace(authCode, "<private>") : jsonStr;
|
||||
|
||||
if (window._ws) {
|
||||
window._log('Sending JSON: ' + jsonStrSanitized);
|
||||
window._ws.send(jsonStr);
|
||||
} else {
|
||||
window._log('Error: No WebSocket. Discarding JSON message: ' + jsonStrSanitized);
|
||||
}
|
||||
};
|
||||
|
||||
// permanentAuthCode is optional. If this is the first time they've selected GPMDP, we won't have a
|
||||
// permanent code yet.
|
||||
window.connect = permanentAuthCode => {
|
||||
// Reset the connection state.
|
||||
window._requestID = 1;
|
||||
|
||||
// Close the existing connection if we're already connected.
|
||||
window.disconnect();
|
||||
|
||||
// Create the new connection.
|
||||
window._ws = new WebSocket('ws://localhost:5672');
|
||||
|
||||
window._ws.onmessage = event => {
|
||||
// Pass the message along to BGMGooglePlayMusicDesktopPlayerConnection.
|
||||
let reply = JSON.parse(event.data);
|
||||
window.webkit.messageHandlers.gpmdp.postMessage(reply);
|
||||
};
|
||||
|
||||
window._wsPromise = new Promise((resolve, reject) => {
|
||||
window._ws.onopen = () => {
|
||||
// Send GPMDP the initial connection message.
|
||||
if (permanentAuthCode) {
|
||||
window._log('Connecting with auth code');
|
||||
window.sendPermanentAuthCode(permanentAuthCode);
|
||||
} else {
|
||||
// Since we don't have an auth code, it will display a four-digit code and reply
|
||||
// telling us to ask the user to type it into Background Music.
|
||||
window._log('Connecting without auth code');
|
||||
|
||||
window._sendJSONImmediate({
|
||||
'namespace': 'connect',
|
||||
'method': 'connect',
|
||||
'arguments': ['Background Music']
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window._ws.onerror = error => {
|
||||
// Report the error to BGMGooglePlayMusicDesktopPlayerConnection.
|
||||
window.webkit.messageHandlers.error.postMessage(error);
|
||||
// Reject the connection promise.
|
||||
reject(error);
|
||||
};
|
||||
|
||||
// Store the function that resolves this promise. We resolve it after we finish
|
||||
// authenticating.
|
||||
window._resolveConnectionPromise = resolve;
|
||||
});
|
||||
};
|
||||
|
||||
// Close the connection to GPMDP. Does nothing if we aren't connected.
|
||||
window.disconnect = () => {
|
||||
if (window._ws) {
|
||||
window._log('Closing WebSocket');
|
||||
window._ws.close();
|
||||
window._ws = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Send an authentication code to GPMDP. To send a four-digit code (i.e. one entered by the user),
|
||||
// call this directly. To send a permanent code received from GPMDP, use
|
||||
// window.sendPermanentAuthCode().
|
||||
//
|
||||
// authCode should be percent-encoded.
|
||||
window.sendAuthCode = authCode => {
|
||||
// Percent-decode the auth code string. We pass it percent-encoded just to make sure nothing in
|
||||
// it accidentally gets executed as Javascript.
|
||||
authCode = window.decodeURIComponent(authCode);
|
||||
|
||||
window._sendJSONImmediate({
|
||||
'namespace': 'connect',
|
||||
'method': 'connect',
|
||||
'arguments': ['Background Music', authCode]
|
||||
}, authCode);
|
||||
};
|
||||
|
||||
// Send a permanent authentication code, received from GPMDP previously, to GPMDP.
|
||||
window.sendPermanentAuthCode = permanentAuthCode => {
|
||||
window._log('Sending permanent auth code');
|
||||
window.sendAuthCode(permanentAuthCode);
|
||||
// TODO: If the code is rejected, GPMDP will send us a connect message and we'll show the auth
|
||||
// code dialog, but accepting the promise here means some messages we send might get
|
||||
// ignored.
|
||||
window._resolveConnectionPromise();
|
||||
};
|
||||
|
||||
// Ask GPMDP to send us its current playback state (playing, paused or stopped).
|
||||
window.requestPlaybackState = () => {
|
||||
window._sendJSON({
|
||||
'namespace': 'playback',
|
||||
'method': 'getPlaybackState',
|
||||
// We don't send any other types of request, so the ID we send only needs to be unique.
|
||||
'requestID': window._requestID++
|
||||
});
|
||||
};
|
||||
|
||||
// Tell GPMDP to toggle between playing and paused.
|
||||
window.playPause = () => {
|
||||
window._sendJSON({
|
||||
'namespace': 'playback',
|
||||
'method': 'playPause'
|
||||
});
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
window.webkit.messageHandlers.log.postMessage('Error: ' + JSON.stringify(error));
|
||||
window.webkit.messageHandlers.log.postMessage(JSON.stringify(error.stack));
|
||||
window.webkit.messageHandlers.error.postMessage(error);
|
||||
}
|
||||
|
||||
// Return an empty string as returning some types can cause an error when this Javascript is loaded
|
||||
// into the WKWebView.
|
||||
""
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* Music.h
|
||||
*
|
||||
* Generated with
|
||||
* sdef /System/Applications/Music.app | sdp -fh --basename Music
|
||||
*/
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <ScriptingBridge/ScriptingBridge.h>
|
||||
|
||||
|
||||
@class MusicApplication, MusicItem, MusicAirPlayDevice, MusicArtwork, MusicEncoder, MusicEQPreset, MusicPlaylist, MusicAudioCDPlaylist, MusicLibraryPlaylist, MusicRadioTunerPlaylist, MusicSource, MusicSubscriptionPlaylist, MusicTrack, MusicAudioCDTrack, MusicFileTrack, MusicSharedTrack, MusicURLTrack, MusicUserPlaylist, MusicFolderPlaylist, MusicVisual, MusicWindow, MusicBrowserWindow, MusicEQWindow, MusicMiniplayerWindow, MusicPlaylistWindow, MusicVideoWindow;
|
||||
|
||||
enum MusicEKnd {
|
||||
MusicEKndTrackListing = 'kTrk' /* a basic listing of tracks within a playlist */,
|
||||
MusicEKndAlbumListing = 'kAlb' /* a listing of a playlist grouped by album */,
|
||||
MusicEKndCdInsert = 'kCDi' /* a printout of the playlist for jewel case inserts */
|
||||
};
|
||||
typedef enum MusicEKnd MusicEKnd;
|
||||
|
||||
enum MusicEnum {
|
||||
MusicEnumStandard = 'lwst' /* Standard PostScript error handling */,
|
||||
MusicEnumDetailed = 'lwdt' /* print a detailed report of PostScript errors */
|
||||
};
|
||||
typedef enum MusicEnum MusicEnum;
|
||||
|
||||
enum MusicEPlS {
|
||||
MusicEPlSStopped = 'kPSS',
|
||||
MusicEPlSPlaying = 'kPSP',
|
||||
MusicEPlSPaused = 'kPSp',
|
||||
MusicEPlSFastForwarding = 'kPSF',
|
||||
MusicEPlSRewinding = 'kPSR'
|
||||
};
|
||||
typedef enum MusicEPlS MusicEPlS;
|
||||
|
||||
enum MusicERpt {
|
||||
MusicERptOff = 'kRpO',
|
||||
MusicERptOne = 'kRp1',
|
||||
MusicERptAll = 'kAll'
|
||||
};
|
||||
typedef enum MusicERpt MusicERpt;
|
||||
|
||||
enum MusicEShM {
|
||||
MusicEShMSongs = 'kShS',
|
||||
MusicEShMAlbums = 'kShA',
|
||||
MusicEShMGroupings = 'kShG'
|
||||
};
|
||||
typedef enum MusicEShM MusicEShM;
|
||||
|
||||
enum MusicESrc {
|
||||
MusicESrcLibrary = 'kLib',
|
||||
MusicESrcIPod = 'kPod',
|
||||
MusicESrcAudioCD = 'kACD',
|
||||
MusicESrcMP3CD = 'kMCD',
|
||||
MusicESrcRadioTuner = 'kTun',
|
||||
MusicESrcSharedLibrary = 'kShd',
|
||||
MusicESrcITunesStore = 'kITS',
|
||||
MusicESrcUnknown = 'kUnk'
|
||||
};
|
||||
typedef enum MusicESrc MusicESrc;
|
||||
|
||||
enum MusicESrA {
|
||||
MusicESrAAlbums = 'kSrL' /* albums only */,
|
||||
MusicESrAAll = 'kAll' /* all text fields */,
|
||||
MusicESrAArtists = 'kSrR' /* artists only */,
|
||||
MusicESrAComposers = 'kSrC' /* composers only */,
|
||||
MusicESrADisplayed = 'kSrV' /* visible text fields */,
|
||||
MusicESrASongs = 'kSrS' /* song names only */
|
||||
};
|
||||
typedef enum MusicESrA MusicESrA;
|
||||
|
||||
enum MusicESpK {
|
||||
MusicESpKNone = 'kNon',
|
||||
MusicESpKFolder = 'kSpF',
|
||||
MusicESpKGenius = 'kSpG',
|
||||
MusicESpKLibrary = 'kSpL',
|
||||
MusicESpKMusic = 'kSpZ',
|
||||
MusicESpKPurchasedMusic = 'kSpM'
|
||||
};
|
||||
typedef enum MusicESpK MusicESpK;
|
||||
|
||||
enum MusicEMdK {
|
||||
MusicEMdKSong = 'kMdS' /* music track */,
|
||||
MusicEMdKMusicVideo = 'kVdV' /* music video track */,
|
||||
MusicEMdKUnknown = 'kUnk'
|
||||
};
|
||||
typedef enum MusicEMdK MusicEMdK;
|
||||
|
||||
enum MusicERtK {
|
||||
MusicERtKUser = 'kRtU' /* user-specified rating */,
|
||||
MusicERtKComputed = 'kRtC' /* iTunes-computed rating */
|
||||
};
|
||||
typedef enum MusicERtK MusicERtK;
|
||||
|
||||
enum MusicEAPD {
|
||||
MusicEAPDComputer = 'kAPC',
|
||||
MusicEAPDAirPortExpress = 'kAPX',
|
||||
MusicEAPDAppleTV = 'kAPT',
|
||||
MusicEAPDAirPlayDevice = 'kAPO',
|
||||
MusicEAPDBluetoothDevice = 'kAPB',
|
||||
MusicEAPDHomePod = 'kAPH',
|
||||
MusicEAPDUnknown = 'kAPU'
|
||||
};
|
||||
typedef enum MusicEAPD MusicEAPD;
|
||||
|
||||
enum MusicEClS {
|
||||
MusicEClSUnknown = 'kUnk',
|
||||
MusicEClSPurchased = 'kPur',
|
||||
MusicEClSMatched = 'kMat',
|
||||
MusicEClSUploaded = 'kUpl',
|
||||
MusicEClSIneligible = 'kRej',
|
||||
MusicEClSRemoved = 'kRem',
|
||||
MusicEClSError = 'kErr',
|
||||
MusicEClSDuplicate = 'kDup',
|
||||
MusicEClSSubscription = 'kSub',
|
||||
MusicEClSNoLongerAvailable = 'kRev',
|
||||
MusicEClSNotUploaded = 'kUpP'
|
||||
};
|
||||
typedef enum MusicEClS MusicEClS;
|
||||
|
||||
@protocol MusicGenericMethods
|
||||
|
||||
- (void) printPrintDialog:(BOOL)printDialog withProperties:(NSDictionary *)withProperties kind:(MusicEKnd)kind theme:(NSString *)theme; // Print the specified object(s)
|
||||
- (void) close; // Close an object
|
||||
- (void) delete; // Delete an element from an object
|
||||
- (SBObject *) duplicateTo:(SBObject *)to; // Duplicate one or more object(s)
|
||||
- (BOOL) exists; // Verify if an object exists
|
||||
- (void) open; // Open the specified object(s)
|
||||
- (void) save; // Save the specified object(s)
|
||||
- (void) playOnce:(BOOL)once; // play the current track or the specified track or file.
|
||||
- (void) select; // select the specified object(s)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* iTunes Suite
|
||||
*/
|
||||
|
||||
// The application program
|
||||
@interface MusicApplication : SBApplication
|
||||
|
||||
- (SBElementArray<MusicAirPlayDevice *> *) AirPlayDevices;
|
||||
- (SBElementArray<MusicBrowserWindow *> *) browserWindows;
|
||||
- (SBElementArray<MusicEncoder *> *) encoders;
|
||||
- (SBElementArray<MusicEQPreset *> *) EQPresets;
|
||||
- (SBElementArray<MusicEQWindow *> *) EQWindows;
|
||||
- (SBElementArray<MusicMiniplayerWindow *> *) miniplayerWindows;
|
||||
- (SBElementArray<MusicPlaylist *> *) playlists;
|
||||
- (SBElementArray<MusicPlaylistWindow *> *) playlistWindows;
|
||||
- (SBElementArray<MusicSource *> *) sources;
|
||||
- (SBElementArray<MusicTrack *> *) tracks;
|
||||
- (SBElementArray<MusicVideoWindow *> *) videoWindows;
|
||||
- (SBElementArray<MusicVisual *> *) visuals;
|
||||
- (SBElementArray<MusicWindow *> *) windows;
|
||||
|
||||
@property (readonly) BOOL AirPlayEnabled; // is AirPlay currently enabled?
|
||||
@property (readonly) BOOL converting; // is a track currently being converted?
|
||||
@property (copy) NSArray<MusicAirPlayDevice *> *currentAirPlayDevices; // the currently selected AirPlay device(s)
|
||||
@property (copy) MusicEncoder *currentEncoder; // the currently selected encoder (MP3, AIFF, WAV, etc.)
|
||||
@property (copy) MusicEQPreset *currentEQPreset; // the currently selected equalizer preset
|
||||
@property (copy, readonly) MusicPlaylist *currentPlaylist; // the playlist containing the currently targeted track
|
||||
@property (copy, readonly) NSString *currentStreamTitle; // the name of the current song in the playing stream (provided by streaming server)
|
||||
@property (copy, readonly) NSString *currentStreamURL; // the URL of the playing stream or streaming web site (provided by streaming server)
|
||||
@property (copy, readonly) MusicTrack *currentTrack; // the current targeted track
|
||||
@property (copy) MusicVisual *currentVisual; // the currently selected visual plug-in
|
||||
@property BOOL EQEnabled; // is the equalizer enabled?
|
||||
@property BOOL fixedIndexing; // true if all AppleScript track indices should be independent of the play order of the owning playlist.
|
||||
@property BOOL frontmost; // is iTunes the frontmost application?
|
||||
@property BOOL fullScreen; // are visuals displayed using the entire screen?
|
||||
@property (copy, readonly) NSString *name; // the name of the application
|
||||
@property BOOL mute; // has the sound output been muted?
|
||||
@property double playerPosition; // the player’s position within the currently playing track in seconds.
|
||||
@property (readonly) MusicEPlS playerState; // is iTunes stopped, paused, or playing?
|
||||
@property (copy, readonly) SBObject *selection; // the selection visible to the user
|
||||
@property BOOL shuffleEnabled; // are songs played in random order?
|
||||
@property MusicEShM shuffleMode; // the playback shuffle mode
|
||||
@property MusicERpt songRepeat; // the playback repeat mode
|
||||
@property NSInteger soundVolume; // the sound output volume (0 = minimum, 100 = maximum)
|
||||
@property (copy, readonly) NSString *version; // the version of iTunes
|
||||
@property BOOL visualsEnabled; // are visuals currently being displayed?
|
||||
|
||||
- (void) printPrintDialog:(BOOL)printDialog withProperties:(NSDictionary *)withProperties kind:(MusicEKnd)kind theme:(NSString *)theme; // Print the specified object(s)
|
||||
- (void) run; // Run iTunes
|
||||
- (void) quit; // Quit iTunes
|
||||
- (MusicTrack *) add:(NSArray<NSURL *> *)x to:(SBObject *)to; // add one or more files to a playlist
|
||||
- (void) backTrack; // reposition to beginning of current track or go to previous track if already at start of current track
|
||||
- (MusicTrack *) convert:(NSArray<SBObject *> *)x; // convert one or more files or tracks
|
||||
- (void) fastForward; // skip forward in a playing track
|
||||
- (void) nextTrack; // advance to the next track in the current playlist
|
||||
- (void) pause; // pause playback
|
||||
- (void) playOnce:(BOOL)once; // play the current track or the specified track or file.
|
||||
- (void) playpause; // toggle the playing/paused state of the current track
|
||||
- (void) previousTrack; // return to the previous track in the current playlist
|
||||
- (void) resume; // disable fast forward/rewind and resume playback, if playing.
|
||||
- (void) rewind; // skip backwards in a playing track
|
||||
- (void) stop; // stop playback
|
||||
- (void) openLocation:(NSString *)x; // Opens a Music Store or audio stream URL
|
||||
|
||||
@end
|
||||
|
||||
// an item
|
||||
@interface MusicItem : SBObject <MusicGenericMethods>
|
||||
|
||||
@property (copy, readonly) SBObject *container; // the container of the item
|
||||
- (NSInteger) id; // the id of the item
|
||||
@property (readonly) NSInteger index; // The index of the item in internal application order.
|
||||
@property (copy) NSString *name; // the name of the item
|
||||
@property (copy, readonly) NSString *persistentID; // the id of the item as a hexadecimal string. This id does not change over time.
|
||||
@property (copy) NSDictionary *properties; // every property of the item
|
||||
|
||||
- (void) download; // download a cloud track or playlist
|
||||
- (void) reveal; // reveal and select a track or playlist
|
||||
|
||||
@end
|
||||
|
||||
// an AirPlay device
|
||||
@interface MusicAirPlayDevice : MusicItem
|
||||
|
||||
@property (readonly) BOOL active; // is the device currently being played to?
|
||||
@property (readonly) BOOL available; // is the device currently available?
|
||||
@property (readonly) MusicEAPD kind; // the kind of the device
|
||||
@property (copy, readonly) NSString *networkAddress; // the network (MAC) address of the device
|
||||
- (BOOL) protected; // is the device password- or passcode-protected?
|
||||
@property BOOL selected; // is the device currently selected?
|
||||
@property (readonly) BOOL supportsAudio; // does the device support audio playback?
|
||||
@property (readonly) BOOL supportsVideo; // does the device support video playback?
|
||||
@property NSInteger soundVolume; // the output volume for the device (0 = minimum, 100 = maximum)
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a piece of art within a track or playlist
|
||||
@interface MusicArtwork : MusicItem
|
||||
|
||||
@property (copy) NSImage *data; // data for this artwork, in the form of a picture
|
||||
@property (copy) NSString *objectDescription; // description of artwork as a string
|
||||
@property (readonly) BOOL downloaded; // was this artwork downloaded by iTunes?
|
||||
@property (copy, readonly) NSNumber *format; // the data format for this piece of artwork
|
||||
@property NSInteger kind; // kind or purpose of this piece of artwork
|
||||
@property (copy) NSData *rawData; // data for this artwork, in original format
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// converts a track to a specific file format
|
||||
@interface MusicEncoder : MusicItem
|
||||
|
||||
@property (copy, readonly) NSString *format; // the data format created by the encoder
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// equalizer preset configuration
|
||||
@interface MusicEQPreset : MusicItem
|
||||
|
||||
@property double band1; // the equalizer 32 Hz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band2; // the equalizer 64 Hz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band3; // the equalizer 125 Hz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band4; // the equalizer 250 Hz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band5; // the equalizer 500 Hz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band6; // the equalizer 1 kHz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band7; // the equalizer 2 kHz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band8; // the equalizer 4 kHz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band9; // the equalizer 8 kHz band level (-12.0 dB to +12.0 dB)
|
||||
@property double band10; // the equalizer 16 kHz band level (-12.0 dB to +12.0 dB)
|
||||
@property (readonly) BOOL modifiable; // can this preset be modified?
|
||||
@property double preamp; // the equalizer preamp level (-12.0 dB to +12.0 dB)
|
||||
@property BOOL updateTracks; // should tracks which refer to this preset be updated when the preset is renamed or deleted?
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a list of songs/streams
|
||||
@interface MusicPlaylist : MusicItem
|
||||
|
||||
- (SBElementArray<MusicTrack *> *) tracks;
|
||||
- (SBElementArray<MusicArtwork *> *) artworks;
|
||||
|
||||
@property (copy) NSString *objectDescription; // the description of the playlist
|
||||
@property BOOL disliked; // is this playlist disliked?
|
||||
@property (readonly) NSInteger duration; // the total length of all songs (in seconds)
|
||||
@property (copy) NSString *name; // the name of the playlist
|
||||
@property BOOL loved; // is this playlist loved?
|
||||
@property (copy, readonly) MusicPlaylist *parent; // folder which contains this playlist (if any)
|
||||
@property (readonly) NSInteger size; // the total size of all songs (in bytes)
|
||||
@property (readonly) MusicESpK specialKind; // special playlist kind
|
||||
@property (copy, readonly) NSString *time; // the length of all songs in MM:SS format
|
||||
@property (readonly) BOOL visible; // is this playlist visible in the Source list?
|
||||
|
||||
- (void) moveTo:(SBObject *)to; // Move playlist(s) to a new location
|
||||
- (MusicTrack *) searchFor:(NSString *)for_ only:(MusicESrA)only; // search a playlist for tracks matching the search string. Identical to entering search text in the Search field in iTunes.
|
||||
|
||||
@end
|
||||
|
||||
// a playlist representing an audio CD
|
||||
@interface MusicAudioCDPlaylist : MusicPlaylist
|
||||
|
||||
- (SBElementArray<MusicAudioCDTrack *> *) audioCDTracks;
|
||||
|
||||
@property (copy) NSString *artist; // the artist of the CD
|
||||
@property BOOL compilation; // is this CD a compilation album?
|
||||
@property (copy) NSString *composer; // the composer of the CD
|
||||
@property NSInteger discCount; // the total number of discs in this CD’s album
|
||||
@property NSInteger discNumber; // the index of this CD disc in the source album
|
||||
@property (copy) NSString *genre; // the genre of the CD
|
||||
@property NSInteger year; // the year the album was recorded/released
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the master music library playlist
|
||||
@interface MusicLibraryPlaylist : MusicPlaylist
|
||||
|
||||
- (SBElementArray<MusicFileTrack *> *) fileTracks;
|
||||
- (SBElementArray<MusicURLTrack *> *) URLTracks;
|
||||
- (SBElementArray<MusicSharedTrack *> *) sharedTracks;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the radio tuner playlist
|
||||
@interface MusicRadioTunerPlaylist : MusicPlaylist
|
||||
|
||||
- (SBElementArray<MusicURLTrack *> *) URLTracks;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a music source (music library, CD, device, etc.)
|
||||
@interface MusicSource : MusicItem
|
||||
|
||||
- (SBElementArray<MusicAudioCDPlaylist *> *) audioCDPlaylists;
|
||||
- (SBElementArray<MusicLibraryPlaylist *> *) libraryPlaylists;
|
||||
- (SBElementArray<MusicPlaylist *> *) playlists;
|
||||
- (SBElementArray<MusicRadioTunerPlaylist *> *) radioTunerPlaylists;
|
||||
- (SBElementArray<MusicSubscriptionPlaylist *> *) subscriptionPlaylists;
|
||||
- (SBElementArray<MusicUserPlaylist *> *) userPlaylists;
|
||||
|
||||
@property (readonly) long long capacity; // the total size of the source if it has a fixed size
|
||||
@property (readonly) long long freeSpace; // the free space on the source if it has a fixed size
|
||||
@property (readonly) MusicESrc kind;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a subscription playlist from Apple Music
|
||||
@interface MusicSubscriptionPlaylist : MusicPlaylist
|
||||
|
||||
- (SBElementArray<MusicFileTrack *> *) fileTracks;
|
||||
- (SBElementArray<MusicURLTrack *> *) URLTracks;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// playable audio source
|
||||
@interface MusicTrack : MusicItem
|
||||
|
||||
- (SBElementArray<MusicArtwork *> *) artworks;
|
||||
|
||||
@property (copy) NSString *album; // the album name of the track
|
||||
@property (copy) NSString *albumArtist; // the album artist of the track
|
||||
@property BOOL albumDisliked; // is the album for this track disliked?
|
||||
@property BOOL albumLoved; // is the album for this track loved?
|
||||
@property NSInteger albumRating; // the rating of the album for this track (0 to 100)
|
||||
@property (readonly) MusicERtK albumRatingKind; // the rating kind of the album rating for this track
|
||||
@property (copy) NSString *artist; // the artist/source of the track
|
||||
@property (readonly) NSInteger bitRate; // the bit rate of the track (in kbps)
|
||||
@property double bookmark; // the bookmark time of the track in seconds
|
||||
@property BOOL bookmarkable; // is the playback position for this track remembered?
|
||||
@property NSInteger bpm; // the tempo of this track in beats per minute
|
||||
@property (copy) NSString *category; // the category of the track
|
||||
@property (readonly) MusicEClS cloudStatus; // the iCloud status of the track
|
||||
@property (copy) NSString *comment; // freeform notes about the track
|
||||
@property BOOL compilation; // is this track from a compilation album?
|
||||
@property (copy) NSString *composer; // the composer of the track
|
||||
@property (readonly) NSInteger databaseID; // the common, unique ID for this track. If two tracks in different playlists have the same database ID, they are sharing the same data.
|
||||
@property (copy, readonly) NSDate *dateAdded; // the date the track was added to the playlist
|
||||
@property (copy) NSString *objectDescription; // the description of the track
|
||||
@property NSInteger discCount; // the total number of discs in the source album
|
||||
@property NSInteger discNumber; // the index of the disc containing this track on the source album
|
||||
@property BOOL disliked; // is this track disliked?
|
||||
@property (copy, readonly) NSString *downloaderAppleID; // the Apple ID of the person who downloaded this track
|
||||
@property (copy, readonly) NSString *downloaderName; // the name of the person who downloaded this track
|
||||
@property (readonly) double duration; // the length of the track in seconds
|
||||
@property BOOL enabled; // is this track checked for playback?
|
||||
@property (copy) NSString *episodeID; // the episode ID of the track
|
||||
@property NSInteger episodeNumber; // the episode number of the track
|
||||
@property (copy) NSString *EQ; // the name of the EQ preset of the track
|
||||
@property double finish; // the stop time of the track in seconds
|
||||
@property BOOL gapless; // is this track from a gapless album?
|
||||
@property (copy) NSString *genre; // the music/audio genre (category) of the track
|
||||
@property (copy) NSString *grouping; // the grouping (piece) of the track. Generally used to denote movements within a classical work.
|
||||
@property (copy, readonly) NSString *kind; // a text description of the track
|
||||
@property (copy) NSString *longDescription;
|
||||
@property BOOL loved; // is this track loved?
|
||||
@property (copy) NSString *lyrics; // the lyrics of the track
|
||||
@property MusicEMdK mediaKind; // the media kind of the track
|
||||
@property (copy, readonly) NSDate *modificationDate; // the modification date of the content of this track
|
||||
@property (copy) NSString *movement; // the movement name of the track
|
||||
@property NSInteger movementCount; // the total number of movements in the work
|
||||
@property NSInteger movementNumber; // the index of the movement in the work
|
||||
@property NSInteger playedCount; // number of times this track has been played
|
||||
@property (copy) NSDate *playedDate; // the date and time this track was last played
|
||||
@property (copy, readonly) NSString *purchaserAppleID; // the Apple ID of the person who purchased this track
|
||||
@property (copy, readonly) NSString *purchaserName; // the name of the person who purchased this track
|
||||
@property NSInteger rating; // the rating of this track (0 to 100)
|
||||
@property (readonly) MusicERtK ratingKind; // the rating kind of this track
|
||||
@property (copy, readonly) NSDate *releaseDate; // the release date of this track
|
||||
@property (readonly) NSInteger sampleRate; // the sample rate of the track (in Hz)
|
||||
@property NSInteger seasonNumber; // the season number of the track
|
||||
@property BOOL shufflable; // is this track included when shuffling?
|
||||
@property NSInteger skippedCount; // number of times this track has been skipped
|
||||
@property (copy) NSDate *skippedDate; // the date and time this track was last skipped
|
||||
@property (copy) NSString *show; // the show name of the track
|
||||
@property (copy) NSString *sortAlbum; // override string to use for the track when sorting by album
|
||||
@property (copy) NSString *sortArtist; // override string to use for the track when sorting by artist
|
||||
@property (copy) NSString *sortAlbumArtist; // override string to use for the track when sorting by album artist
|
||||
@property (copy) NSString *sortName; // override string to use for the track when sorting by name
|
||||
@property (copy) NSString *sortComposer; // override string to use for the track when sorting by composer
|
||||
@property (copy) NSString *sortShow; // override string to use for the track when sorting by show name
|
||||
@property (readonly) long long size; // the size of the track (in bytes)
|
||||
@property double start; // the start time of the track in seconds
|
||||
@property (copy, readonly) NSString *time; // the length of the track in MM:SS format
|
||||
@property NSInteger trackCount; // the total number of tracks on the source album
|
||||
@property NSInteger trackNumber; // the index of the track on the source album
|
||||
@property BOOL unplayed; // is this track unplayed?
|
||||
@property NSInteger volumeAdjustment; // relative volume adjustment of the track (-100% to 100%)
|
||||
@property (copy) NSString *work; // the work name of the track
|
||||
@property NSInteger year; // the year the track was recorded/released
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a track on an audio CD
|
||||
@interface MusicAudioCDTrack : MusicTrack
|
||||
|
||||
@property (copy, readonly) NSURL *location; // the location of the file represented by this track
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a track representing an audio file (MP3, AIFF, etc.)
|
||||
@interface MusicFileTrack : MusicTrack
|
||||
|
||||
@property (copy) NSURL *location; // the location of the file represented by this track
|
||||
|
||||
- (void) refresh; // update file track information from the current information in the track’s file
|
||||
|
||||
@end
|
||||
|
||||
// a track residing in a shared library
|
||||
@interface MusicSharedTrack : MusicTrack
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a track representing a network stream
|
||||
@interface MusicURLTrack : MusicTrack
|
||||
|
||||
@property (copy) NSString *address; // the URL for this track
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// custom playlists created by the user
|
||||
@interface MusicUserPlaylist : MusicPlaylist
|
||||
|
||||
- (SBElementArray<MusicFileTrack *> *) fileTracks;
|
||||
- (SBElementArray<MusicURLTrack *> *) URLTracks;
|
||||
- (SBElementArray<MusicSharedTrack *> *) sharedTracks;
|
||||
|
||||
@property BOOL shared; // is this playlist shared?
|
||||
@property (readonly) BOOL smart; // is this a Smart Playlist?
|
||||
@property (readonly) BOOL genius; // is this a Genius Playlist?
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a folder that contains other playlists
|
||||
@interface MusicFolderPlaylist : MusicUserPlaylist
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a visual plug-in
|
||||
@interface MusicVisual : MusicItem
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// any window
|
||||
@interface MusicWindow : MusicItem
|
||||
|
||||
@property NSRect bounds; // the boundary rectangle for the window
|
||||
@property (readonly) BOOL closeable; // does the window have a close button?
|
||||
@property (readonly) BOOL collapseable; // does the window have a collapse button?
|
||||
@property BOOL collapsed; // is the window collapsed?
|
||||
@property BOOL fullScreen; // is the window full screen?
|
||||
@property NSPoint position; // the upper left position of the window
|
||||
@property (readonly) BOOL resizable; // is the window resizable?
|
||||
@property BOOL visible; // is the window visible?
|
||||
@property (readonly) BOOL zoomable; // is the window zoomable?
|
||||
@property BOOL zoomed; // is the window zoomed?
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the main iTunes window
|
||||
@interface MusicBrowserWindow : MusicWindow
|
||||
|
||||
@property (copy, readonly) SBObject *selection; // the selected songs
|
||||
@property (copy) MusicPlaylist *view; // the playlist currently displayed in the window
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the iTunes equalizer window
|
||||
@interface MusicEQWindow : MusicWindow
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the miniplayer window
|
||||
@interface MusicMiniplayerWindow : MusicWindow
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// a sub-window showing a single playlist
|
||||
@interface MusicPlaylistWindow : MusicWindow
|
||||
|
||||
@property (copy, readonly) SBObject *selection; // the selected songs
|
||||
@property (copy, readonly) MusicPlaylist *view; // the playlist displayed in the window
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// the video window
|
||||
@interface MusicVideoWindow : MusicWindow
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -221,7 +221,7 @@ typedef enum VLCEnum VLCEnum;
|
||||
@property NSInteger audioVolume; // The volume of the current playlist item from 0 to 4, where 4 is 400%
|
||||
@property NSInteger currentTime; // The current time of the current playlist item in seconds.
|
||||
@property (readonly) NSInteger durationOfCurrentItem; // The duration of the current playlist item in seconds.
|
||||
@property BOOL fullscreenMode; // indicates wheter fullscreen is enabled or not
|
||||
@property BOOL fullscreenMode; // indicates whether fullscreen is enabled or not
|
||||
@property (readonly) BOOL muted; // Is VLC currently muted?
|
||||
@property (copy, readonly) NSString *nameOfCurrentItem; // Name of the current playlist item.
|
||||
@property (copy, readonly) NSString *pathOfCurrentItem; // Path to the current playlist item.
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- (void) rewindForwardFast; // Rewind current track forward fast.
|
||||
- (void) rewindBackward; // Rewind current track backward.
|
||||
- (void) rewindBackwardFast; // Rewind current track backward fast.
|
||||
- (void) increasVolume; // Increas volume.
|
||||
- (void) increasVolume; // Increase volume.
|
||||
- (void) decreaseVolume; // Decrease volume.
|
||||
- (void) showHidePlaylist; // Show/Hide playlist.
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
@property (copy, readonly) NSString *album; // Current track album.
|
||||
@property (copy, readonly) NSString *uniqueID; // Unique identifier for the current track.
|
||||
@property double currentTime; // The current playback position.
|
||||
@property (readonly) double totalTime; // The total time of the currenty playing track.
|
||||
@property (readonly) double totalTime; // The total time of the currently playing track.
|
||||
@property double playerVolume; // Player volume (0.0 to 1.0)
|
||||
@property NSInteger repeatState; // Player repeat state (none = 0, repeat one = 1, repeat all = 2)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMAboutPanel.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2024 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
static NSInteger const kVersionLabelTag = 1;
|
||||
static NSInteger const kCopyrightLabelTag = 2;
|
||||
static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
static NSInteger const kContributorsLabelTag = 4;
|
||||
|
||||
@implementation BGMAboutPanel {
|
||||
NSPanel* aboutPanel;
|
||||
@@ -42,6 +43,7 @@ static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
NSTextField* versionLabel;
|
||||
NSTextField* copyrightLabel;
|
||||
NSTextField* websiteLabel;
|
||||
NSTextField* contributorsLabel;
|
||||
|
||||
NSTextView* licenseView;
|
||||
}
|
||||
@@ -53,6 +55,7 @@ static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
versionLabel = [[aboutPanel contentView] viewWithTag:kVersionLabelTag];
|
||||
copyrightLabel = [[aboutPanel contentView] viewWithTag:kCopyrightLabelTag];
|
||||
websiteLabel = [[aboutPanel contentView] viewWithTag:kProjectWebsiteLabelTag];
|
||||
contributorsLabel = [[aboutPanel contentView] viewWithTag:kContributorsLabelTag];
|
||||
|
||||
licenseView = inLicenseView;
|
||||
|
||||
@@ -83,7 +86,10 @@ static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
[[bundle infoDictionary] objectForKey:@"NSHumanReadableCopyright"];
|
||||
|
||||
if (copyrightNotice) {
|
||||
copyrightLabel.stringValue = (NSString*)copyrightNotice;
|
||||
// Remove the part that we replace with a link.
|
||||
copyrightLabel.stringValue =
|
||||
[((NSString*)copyrightNotice) stringByReplacingOccurrencesOfString:contributorsLabel.stringValue
|
||||
withString:@""];
|
||||
}
|
||||
|
||||
// Project website link label
|
||||
@@ -97,6 +103,18 @@ static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
attributes:@{ NSLinkAttributeName: projectURL,
|
||||
NSFontAttributeName: linkFont }];
|
||||
|
||||
// Contributors link label
|
||||
// TODO: Proper credits (i.e. in the app instead of just a link)
|
||||
contributorsLabel.selectable = YES;
|
||||
contributorsLabel.allowsEditingTextAttributes = YES;
|
||||
|
||||
NSString* contributorsURL = [NSString stringWithUTF8String:kBGMContributorsURL];
|
||||
NSFont* cLinkFont = contributorsLabel.font ? contributorsLabel.font : [NSFont labelFontOfSize:0.0];
|
||||
contributorsLabel.attributedStringValue =
|
||||
[[NSAttributedString alloc] initWithString:contributorsLabel.stringValue
|
||||
attributes:@{ NSLinkAttributeName: contributorsURL,
|
||||
NSFontAttributeName: cLinkFont }];
|
||||
|
||||
// Load the text of the license into the text view
|
||||
NSString* __nullable licensePath = [bundle pathForResource:@"LICENSE" ofType:nil];
|
||||
|
||||
@@ -122,9 +140,25 @@ static NSInteger const kProjectWebsiteLabelTag = 3;
|
||||
|
||||
- (void) show {
|
||||
DebugMsg("BGMAboutPanel::showAboutPanel: Opening \"About Background Music\" panel");
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
// We have to make aboutPanel visible before calling [NSApp activateIgnoringOtherApps:YES]
|
||||
// or the app won't be activated the first time (not sure why it only happens the first
|
||||
// time) and aboutPanel won't open. WindowServer logs this explanation:
|
||||
// 0[SetFrontProcessWithInfo]: CPS: Rejecting the request for pid 1234 due to the activation count being 0; launch ts=19302059379458, current time=19314267188375, window count = 0.
|
||||
[aboutPanel setIsVisible:YES];
|
||||
[aboutPanel makeKeyAndOrderFront:self];
|
||||
|
||||
// On macOS 14.4, aboutPanel needs "Release When Closed" unchecked in MainMenu.xib. Otherwise,
|
||||
// aboutPanel will never open again if you click the close button.
|
||||
|
||||
// This is deprecated for NSApplication.activate, but that stops aboutPanel from ever being shown.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
DebugMsg("BGMAboutPanel::showAboutPanel: Finished opening panel. "
|
||||
"aboutPanel.isVisible %d, aboutPanel.isKeyWindow %d, NSApp.isActive %d",
|
||||
aboutPanel.isVisible,
|
||||
aboutPanel.isKeyWindow,
|
||||
NSApp.isActive);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMAutoPauseMusicPrefs.mm
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2019 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Includes
|
||||
@@ -69,6 +69,7 @@ static NSInteger const kPrefsMenuAutoPauseHeaderTag = 1;
|
||||
action:@selector(handleMusicPlayerChange:)
|
||||
keyEquivalent:@""
|
||||
atIndex:musicPlayerItemsIndex];
|
||||
menuItem.toolTip = musicPlayer.toolTip;
|
||||
|
||||
musicPlayerMenuItems = [musicPlayerMenuItems arrayByAddingObject:menuItem];
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMASApplication.h
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2021 Marcus Wu
|
||||
// Copyright © 2021 Kyle Neideck
|
||||
//
|
||||
// An AppleScript class for volume and pan settings for running applications.
|
||||
//
|
||||
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAppVolumesController.h"
|
||||
|
||||
// System Includes
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface BGMASApplication : NSObject
|
||||
|
||||
- (instancetype) initWithApplication:(NSRunningApplication*)app
|
||||
volumeController:(BGMAppVolumesController*)volumeController
|
||||
parentSpecifier:(NSScriptObjectSpecifier* __nullable)parentSpecifier
|
||||
index:(int)i;
|
||||
|
||||
@property (readonly) NSString* name;
|
||||
@property (readonly) NSString* bundleID;
|
||||
@property int volume;
|
||||
@property int pan;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMASApplication.m
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2021 Marcus Wu
|
||||
// Copyright © 2021 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#import "BGMASApplication.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGM_Types.h"
|
||||
|
||||
@implementation BGMASApplication {
|
||||
NSScriptObjectSpecifier* parentSpecifier;
|
||||
NSRunningApplication *application;
|
||||
BGMAppVolumesController* appVolumesController;
|
||||
int index;
|
||||
}
|
||||
|
||||
- (instancetype) initWithApplication:(NSRunningApplication*)app
|
||||
volumeController:(BGMAppVolumesController*)volumeController
|
||||
parentSpecifier:(NSScriptObjectSpecifier* __nullable)parent
|
||||
index:(int)i {
|
||||
if ((self = [super init])) {
|
||||
parentSpecifier = parent;
|
||||
application = app;
|
||||
appVolumesController = volumeController;
|
||||
index = i;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString*) name {
|
||||
return [NSString stringWithFormat:@"%@", [application localizedName]];
|
||||
}
|
||||
|
||||
- (NSString*) bundleID {
|
||||
return [NSString stringWithFormat:@"%@", [application bundleIdentifier]];
|
||||
}
|
||||
|
||||
- (int) volume {
|
||||
return [appVolumesController getVolumeAndPanForApp:application].volume;
|
||||
}
|
||||
|
||||
- (void) setVolume:(int)vol {
|
||||
BGMAppVolumeAndPan volume = {
|
||||
.volume = vol,
|
||||
.pan = kAppPanNoValue
|
||||
};
|
||||
[appVolumesController setVolumeAndPan:volume forApp:application];
|
||||
}
|
||||
|
||||
- (int) pan {
|
||||
return [appVolumesController getVolumeAndPanForApp:application].pan;
|
||||
}
|
||||
|
||||
- (void) setPan:(int)pan {
|
||||
BGMAppVolumeAndPan thePan = {
|
||||
.volume = -1,
|
||||
.pan = pan
|
||||
};
|
||||
[appVolumesController setVolumeAndPan:thePan forApp:application];
|
||||
}
|
||||
|
||||
- (NSScriptObjectSpecifier* __nullable) objectSpecifier {
|
||||
NSScriptClassDescription* parentClassDescription = [parentSpecifier keyClassDescription];
|
||||
return [[NSNameSpecifier alloc] initWithContainerClassDescription:parentClassDescription
|
||||
containerSpecifier:parentSpecifier
|
||||
key:@"applications"
|
||||
name:self.name];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,8 +9,7 @@
|
||||
<class name="output device"
|
||||
code="aDev"
|
||||
description="A hardware device that can play audio"
|
||||
plural="output devices"
|
||||
inherits="item">
|
||||
plural="output devices">
|
||||
<synonym name="audio device"/>
|
||||
|
||||
<cocoa class="BGMASOutputDevice"/>
|
||||
@@ -19,14 +18,58 @@
|
||||
code="pnam"
|
||||
description="The name of the output device."
|
||||
type="text"
|
||||
access="r"/>
|
||||
access="r">
|
||||
<cocoa key="name"/>
|
||||
</property>
|
||||
|
||||
<property name="selected"
|
||||
code="Slcd"
|
||||
type="boolean"
|
||||
access="rw"
|
||||
description="Is this the device to be used for audio output?">
|
||||
<synonym name="default"/>
|
||||
<synonym name="default"/>
|
||||
<cocoa key="selected"/>
|
||||
</property>
|
||||
</class>
|
||||
|
||||
<class name="audio application"
|
||||
code="aApp"
|
||||
description="An application that can play audio"
|
||||
plural="audio applications">
|
||||
<synonym name="audio app"/>
|
||||
|
||||
<cocoa class="BGMASApplication"/>
|
||||
|
||||
<property name="name"
|
||||
code="pnam"
|
||||
description="The name of the application."
|
||||
type="text"
|
||||
access="r">
|
||||
<cocoa key="name"/>
|
||||
</property>
|
||||
|
||||
<property name="bundleID"
|
||||
code="bdid"
|
||||
description="The bundle ID of the application, e.g. 'com.somecompany.coolapp'."
|
||||
type="text"
|
||||
access="r">
|
||||
<cocoa key="bundleID"/>
|
||||
</property>
|
||||
|
||||
<property name="vol"
|
||||
code="pVol"
|
||||
type="integer"
|
||||
access="rw"
|
||||
description="The volume setting of the application">
|
||||
<cocoa key="volume"/>
|
||||
</property>
|
||||
|
||||
<property name="pan"
|
||||
code="pPan"
|
||||
type="integer"
|
||||
access="rw"
|
||||
description="The pan setting of the application">
|
||||
<cocoa key="pan"/>
|
||||
</property>
|
||||
</class>
|
||||
|
||||
@@ -48,11 +91,23 @@
|
||||
|
||||
<cocoa key="selectedOutputDevice"/>
|
||||
</property>
|
||||
<property name="output volume"
|
||||
type="real"
|
||||
code="oVol"
|
||||
access="rw"
|
||||
description="The main output volume">
|
||||
<synonym name="main volume"/>
|
||||
|
||||
<cocoa key="mainVolume"/>
|
||||
</property>
|
||||
|
||||
<!-- Unintuitively, this is for the array of output devices. -->
|
||||
<element type="output device" access="r">
|
||||
<cocoa key="outputDevices"/>
|
||||
</element>
|
||||
<element type="audio application" access="r">
|
||||
<cocoa key="applications"/>
|
||||
</element>
|
||||
</class>
|
||||
</suite>
|
||||
</dictionary>
|
||||
|
||||
@@ -18,12 +18,18 @@
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
#import "BGMAppDelegate.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
#import "BGMAppVolumesController.h"
|
||||
|
||||
// Local Includes
|
||||
#import "BGMASOutputDevice.h"
|
||||
#import "BGMASApplication.h"
|
||||
|
||||
// System Includes
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -37,6 +43,8 @@
|
||||
|
||||
@property BGMASOutputDevice* selectedOutputDevice;
|
||||
@property (readonly) NSArray<BGMASOutputDevice*>* outputDevices;
|
||||
@property double mainVolume;
|
||||
@property (readonly) NSArray<BGMASApplication*>* applications;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
// BGMApp
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Copyright © 2021 Marcus Wu
|
||||
//
|
||||
|
||||
// Self Include
|
||||
@@ -30,6 +31,7 @@
|
||||
#import "CAHALAudioSystemObject.h"
|
||||
#import "CAAutoDisposer.h"
|
||||
|
||||
const AudioObjectPropertyScope kScope = kAudioDevicePropertyScopeOutput;
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
@@ -43,7 +45,7 @@
|
||||
[key UTF8String]);
|
||||
}
|
||||
|
||||
return [@[@"selectedOutputDevice", @"outputDevices"] containsObject:key];
|
||||
return [@[@"selectedOutputDevice", @"outputDevices", @"mainVolume", @"applications"] containsObject:key];
|
||||
}
|
||||
|
||||
- (BGMASOutputDevice*) selectedOutputDevice {
|
||||
@@ -83,6 +85,30 @@
|
||||
return outputDevices;
|
||||
}
|
||||
|
||||
- (double) mainVolume {
|
||||
BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];
|
||||
return bgmDevice.GetVolumeControlScalarValue(kScope, kMasterChannel);
|
||||
}
|
||||
|
||||
- (void) setMainVolume:(double)mainVolume {
|
||||
BGMAudioDevice bgmDevice = [self.audioDevices bgmDevice];
|
||||
bgmDevice.SetMasterVolumeScalar(kScope, (Float32)mainVolume);
|
||||
[self.outputVolumeSlider setFloatValue:(float)mainVolume];
|
||||
}
|
||||
|
||||
- (NSArray<BGMASApplication*>*) applications {
|
||||
NSArray<NSRunningApplication*>* apps = [[NSWorkspace sharedWorkspace] runningApplications];
|
||||
NSMutableArray<BGMASApplication*>* applications = [NSMutableArray arrayWithCapacity:[apps count]];
|
||||
|
||||
for (UInt32 i = 0; i < [apps count]; i++) {
|
||||
BGMASApplication *app = [[BGMASApplication alloc] initWithApplication:apps[i] volumeController:self.appVolumes parentSpecifier:[self objectSpecifier] index:i];
|
||||
|
||||
[applications addObject:app];
|
||||
}
|
||||
|
||||
return applications;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# _uninstall-non-interactive.sh
|
||||
#
|
||||
# Copyright © 2016 Nick Jacques
|
||||
# Copyright © 2016, 2017 Kyle Neideck
|
||||
# Copyright © 2016, 2017, 2021 Kyle Neideck
|
||||
#
|
||||
# Removes BGMApp, BGMDriver and BGMXPCHelper from the system immediately. Run by uninstall.sh and the Homebrew formula.
|
||||
#
|
||||
@@ -43,8 +43,9 @@ driver_path="/Library/Audio/Plug-Ins/HAL/Background Music Device.driver"
|
||||
xpc_path1="/usr/local/libexec/BGMXPCHelper.xpc"
|
||||
xpc_path2="/Library/Application Support/Background Music/BGMXPCHelper.xpc"
|
||||
|
||||
# Check that files/directories are at most this big before we delete them, just to be safe.
|
||||
max_size_mb_for_rm=15
|
||||
# Check that files/directories are at most this big before we delete them, just to be safe. Note that the bundles can
|
||||
# include debug symbols, e.g. if you use build_and_install.sh, which makes them a lot bigger.
|
||||
max_size_mb_for_rm=30
|
||||
|
||||
file_paths=("${app_path}" "${driver_path}" "${xpc_path1}" "${xpc_path2}")
|
||||
|
||||
@@ -68,8 +69,9 @@ function size_check {
|
||||
[[ "${size}" =~ ^[0-9]+$ ]] && [[ "${size}" -le ${max_size_mb_for_rm} ]]
|
||||
}
|
||||
|
||||
# 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
|
||||
# Ensure that the user can use sudo. (Use `sudo true` instead of `sudo -v` because that causes a
|
||||
# password prompt in Travis CI builds for some reason.)
|
||||
if ! sudo true; then
|
||||
echo "ERROR: This script must be run by a user with administrator (sudo) privileges." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -137,20 +139,21 @@ sleep 2
|
||||
# The extra or-clauses are fallback versions of the command that restarts coreaudiod. Apparently some of these commands
|
||||
# don't work with older versions of launchctl, so I figure there's no harm in trying a bunch of different ways until
|
||||
# one works.
|
||||
(sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
(sudo launchctl kickstart -k system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
sudo killall coreaudiod &>/dev/null || \
|
||||
sudo launchctl kill SIGTERM system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
sudo launchctl kill TERM system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
sudo launchctl kill 15 system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
sudo launchctl kill -15 system/com.apple.audio.coreaudiod &>/dev/null || \
|
||||
(sudo launchctl unload "${coreaudiod_plist}" &>/dev/null && \
|
||||
sudo launchctl load "${coreaudiod_plist}" &>/dev/null) || \
|
||||
sudo killall coreaudiod &>/dev/null)
|
||||
sudo launchctl load "${coreaudiod_plist}" &>/dev/null))
|
||||
|
||||
echo "..."
|
||||
sleep 3
|
||||
|
||||
# TODO: What if they only have one audio device?
|
||||
echo ""
|
||||
echo "${bold}Done! Toggle your audio output device in the Sound section of System Preferences to finish" \
|
||||
echo "${bold}Done! Toggle your audio output device in the Sound section of System Settings to finish" \
|
||||
"uninstalling. (Or just restart your computer.)${normal}"
|
||||
|
||||
|
||||
|
||||
@@ -1,754 +0,0 @@
|
||||
// !$*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 */;
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,38 +0,0 @@
|
||||
<?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
@@ -1,6 +0,0 @@
|
||||
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.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// BGMAppUITests.mm
|
||||
// BGMAppUITests
|
||||
//
|
||||
// Copyright © 2017, 2018 Kyle Neideck
|
||||
// Copyright © 2017, 2018, 2020, 2022 Kyle Neideck
|
||||
//
|
||||
// You might want to use Xcode's UI test recording feature if you add new tests.
|
||||
//
|
||||
@@ -30,8 +30,10 @@
|
||||
// Scripting Bridge Includes
|
||||
#import "BGMApp.h"
|
||||
|
||||
// System Includes
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
// TODO: Skip these tests if macOS SDK 10.11 or higher isn't available.
|
||||
// TODO: Mock BGMDevice and music players.
|
||||
|
||||
#if __clang_major__ >= 9
|
||||
@@ -72,22 +74,55 @@
|
||||
// would fail to start because of a bug in Xcode.
|
||||
app.launchArguments = @[ @"--no-persistent-data", @"--show-dock-icon" ];
|
||||
|
||||
// Make the "Background Music wants to use the microphone" dialog appear every time so the test
|
||||
// doesn't need logic to handle both cases.
|
||||
// TODO: Commented out until acceptMicrophoneAuthorizationDialog work again. See below.
|
||||
// if (@available(macOS 10.15.4, *)) {
|
||||
// [app resetAuthorizationStatusForResource:XCUIProtectedResourceMicrophone];
|
||||
// }
|
||||
|
||||
// Launch BGMApp.
|
||||
[app launch];
|
||||
|
||||
if (![icon waitForExistenceWithTimeout:1.0]) {
|
||||
// TODO: This doesn't seem to be working on macOS 12.4 (21F79). You can click OK manually for
|
||||
// now.
|
||||
// [self acceptMicrophoneAuthorizationDialog];
|
||||
|
||||
if (![icon waitForExistenceWithTimeout:20.0]) {
|
||||
// The status bar icon/button has this type when using older versions of XCTest, so try
|
||||
// both. (Actually, it might depend on the macOS or Xcode version. I'm not sure.)
|
||||
XCUIElement* iconOldType =
|
||||
[app.menuBars childrenMatchingType:XCUIElementTypeMenuBarItem].element;
|
||||
if (![iconOldType waitForExistenceWithTimeout:5.0]) {
|
||||
icon = iconOldType;
|
||||
}
|
||||
[app.menuBars childrenMatchingType:XCUIElementTypeMenuBarItem].element;
|
||||
if ([iconOldType waitForExistenceWithTimeout:20.0]) {
|
||||
NSLog(@"icon = iconOldType");
|
||||
icon = iconOldType;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the initial elements.
|
||||
XCTAssert([app waitForExistenceWithTimeout:10.0]);
|
||||
XCTAssert([icon waitForExistenceWithTimeout:10.0]);
|
||||
XCTAssert([app waitForExistenceWithTimeout:20.0]);
|
||||
XCTAssert([icon waitForExistenceWithTimeout:20.0]);
|
||||
}
|
||||
|
||||
// Clicks the OK button in the "Background Music wants to use the microphone" dialog.
|
||||
- (void) acceptMicrophoneAuthorizationDialog {
|
||||
XCUIApplication* unc =
|
||||
[[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.UserNotificationCenter"];
|
||||
NSLog(@"UserNotificationCenter: %@", unc);
|
||||
XCUIElement* okButton = unc.dialogs.buttons[@"OK"];
|
||||
|
||||
XCTAssert([okButton waitForExistenceWithTimeout:20.0]);
|
||||
|
||||
// This click is failing on GH Actions. No idea why, so try a sleep.
|
||||
(void)[XCTWaiter waitForExpectations:@[[XCTestExpectation new]] timeout:5.0];
|
||||
[okButton click];
|
||||
|
||||
int retries = 10;
|
||||
while (retries > 0 && [okButton waitForExistenceWithTimeout:3.0]) {
|
||||
NSLog(@"Microphone authorization dialog is still open. Trying to click OK again.");
|
||||
[okButton click];
|
||||
retries--;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) tearDown {
|
||||
@@ -99,13 +134,13 @@
|
||||
[menuItems[@"Quit Background Music"] click];
|
||||
|
||||
// BGMApp should quit.
|
||||
XCTAssert(!app.exists);
|
||||
XCTAssertTrue([app waitForState:XCUIApplicationStateNotRunning timeout:10.0]);
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void) testCycleOutputDevices {
|
||||
const int NUM_CYCLES = 2;
|
||||
const int NUM_CYCLES = 1;
|
||||
|
||||
// sbApp lets us use AppleScript to query BGMApp and check the test has made the changes to its
|
||||
// settings we expect.
|
||||
|
||||
+4
-13
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2.7
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This file is part of Background Music.
|
||||
#
|
||||
@@ -16,20 +16,12 @@
|
||||
# along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# travis-skip.py
|
||||
# skip-ui-tests.py
|
||||
# BGMAppUITests
|
||||
#
|
||||
# Copyright (c) 2017 Kyle Neideck
|
||||
# Copyright (c) 2017, 2024 Kyle Neideck
|
||||
#
|
||||
# Skip the UI tests in Travis builds because they aren't supported.
|
||||
#
|
||||
# We can't run the tests on Travis because Xcode needs permission to use the Accessibility API
|
||||
# to control BGMApp. There's no way to set that up programmatically without disabling SIP and
|
||||
# Travis doesn't support that.
|
||||
#
|
||||
# See https://github.com/travis-ci/travis-ci/issues/5819
|
||||
#
|
||||
# TODO: Figure out a better way to do this.
|
||||
# Disables the UI tests. This is mainly useful on CI systems that don't support UI tests.
|
||||
#
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
@@ -46,4 +38,3 @@ tree.getroot().findall(UI_REF_XPATH)[0].set("skipped", "YES")
|
||||
# Save the scheme.
|
||||
tree.write(SCHEME_FILE)
|
||||
|
||||
|
||||
@@ -17,24 +17,28 @@
|
||||
// BGMMusicPlayersUnitTests.mm
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2016-2018 Kyle Neideck
|
||||
// Copyright © 2016-2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Unit include
|
||||
#import "BGMMusicPlayers.h"
|
||||
|
||||
// Local includes
|
||||
#import "BGM_TestUtils.h"
|
||||
|
||||
// BGM includes
|
||||
#import "BGM_Types.h"
|
||||
#import "BGMAudioDeviceManager.h"
|
||||
#import "BGMiTunes.h"
|
||||
#import "BGMVLC.h"
|
||||
#import "BGMDecibel.h"
|
||||
#import "BGMSpotify.h"
|
||||
#import "BGMVLC.h"
|
||||
|
||||
// Local includes
|
||||
#import "BGM_TestUtils.h"
|
||||
#import "MockAudioObject.h"
|
||||
#import "MockAudioObjects.h"
|
||||
|
||||
// System includes
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
// Note that the PublicUtility classes that we use to communicate with the HAL, CAHALAudioObject and
|
||||
@@ -72,32 +76,6 @@
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
class BGMMockBackgroundMusicDevice
|
||||
:
|
||||
public BGMBackgroundMusicDevice
|
||||
{
|
||||
|
||||
public:
|
||||
CFStringRef GetMusicPlayerBundleID() const;
|
||||
void SetMusicPlayerBundleID(CFStringRef inBundleID);
|
||||
|
||||
private:
|
||||
CFStringRef mMusicPlayerBundleID = CFSTR("");
|
||||
|
||||
};
|
||||
|
||||
CFStringRef BGMMockBackgroundMusicDevice::GetMusicPlayerBundleID() const
|
||||
{
|
||||
return mMusicPlayerBundleID;
|
||||
}
|
||||
|
||||
void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID)
|
||||
{
|
||||
mMusicPlayerBundleID = inBundleID;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
@interface BGMMockAudioDeviceManager : BGMAudioDeviceManager
|
||||
@end
|
||||
|
||||
@@ -127,6 +105,10 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
|
||||
- (void) setUp {
|
||||
[super setUp];
|
||||
|
||||
// Mock BGMDevice.
|
||||
MockAudioObjects::CreateMockDevice(kBGMDeviceUID);
|
||||
MockAudioObjects::CreateMockDevice(kBGMDeviceUID_UISounds);
|
||||
|
||||
devices = [BGMMockAudioDeviceManager new];
|
||||
defaults = [BGMMockUserDefaults new];
|
||||
|
||||
@@ -137,9 +119,10 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
|
||||
|
||||
- (void) tearDown {
|
||||
[super tearDown];
|
||||
MockAudioObjects::DestroyMocks();
|
||||
}
|
||||
|
||||
- (void) testNoSelectedMusicPlayerStored {
|
||||
- (void) testNoSelectedMusicPlayerStored_iTunesDefault {
|
||||
// Test the case where the user has never changed the music player preference.
|
||||
|
||||
// Test with iTunes as the default.
|
||||
@@ -156,16 +139,20 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
|
||||
|
||||
XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, [BGMiTunes sharedMusicPlayerID]);
|
||||
XCTAssertEqualObjects(players.selectedMusicPlayer.name, @"iTunes");
|
||||
|
||||
[self resetDevice];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void) testNoSelectedMusicPlayerStored_vlcDefault {
|
||||
// Test the case where the user has never changed the music player preference.
|
||||
|
||||
// Test with VLC as the default.
|
||||
players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
|
||||
defaultMusicPlayerID:vlcID
|
||||
musicPlayerClasses:@[ BGMiTunes.class,
|
||||
BGMVLC.class,
|
||||
BGMDecibel.class ]
|
||||
userDefaults:defaults];
|
||||
BGMMusicPlayers* players =
|
||||
[[BGMMusicPlayers alloc] initWithAudioDevices:devices
|
||||
defaultMusicPlayerID:vlcID
|
||||
musicPlayerClasses:@[ BGMiTunes.class,
|
||||
BGMVLC.class,
|
||||
BGMDecibel.class ]
|
||||
userDefaults:defaults];
|
||||
|
||||
XCTAssertEqual(players.musicPlayers.count, 3);
|
||||
|
||||
@@ -193,15 +180,15 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
|
||||
|
||||
XCTAssertEqualObjects(players.selectedMusicPlayer.musicPlayerID, spotifyID);
|
||||
XCTAssertEqualObjects(players.selectedMusicPlayer.name, @"Spotify");
|
||||
|
||||
[self resetDevice];
|
||||
|
||||
}
|
||||
|
||||
- (void) testUnrecognizedSelectedMusicPlayerInUserDefaults {
|
||||
// If there's an unrecognized ID in user defaults, the default music player should be selected.
|
||||
defaults.selectedPlayerID = [[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-0000-000000000000"];
|
||||
|
||||
// This initializer sets iTunes as the default music player and adds all the other music players.
|
||||
players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
|
||||
userDefaults:defaults];
|
||||
BGMMusicPlayers* players = [[BGMMusicPlayers alloc] initWithAudioDevices:devices
|
||||
userDefaults:defaults];
|
||||
|
||||
XCTAssert(players.musicPlayers.count >= 6);
|
||||
|
||||
@@ -226,10 +213,5 @@ void BGMMockBackgroundMusicDevice::SetMusicPlayerBundleID(CFStringRef inBundleID
|
||||
|
||||
// TODO: Test setting the selectedMusicPlayer property
|
||||
|
||||
- (void) resetDevice {
|
||||
// Reset the mock BGMDevice.
|
||||
[devices bgmDevice].SetMusicPlayerBundleID(CFSTR(""));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMPlayThroughRTLoggerTests.mm
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Unit Include
|
||||
#import "BGMPlayThroughRTLogger.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#import "CARingBuffer.h"
|
||||
|
||||
// System Includes
|
||||
#import <CoreAudio/CoreAudio.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@interface BGMPlayThroughRTLoggerTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation BGMPlayThroughRTLoggerTests
|
||||
{
|
||||
BGMPlayThroughRTLogger* logger;
|
||||
}
|
||||
|
||||
- (void) setUp {
|
||||
[super setUp];
|
||||
logger = new BGMPlayThroughRTLogger;
|
||||
}
|
||||
|
||||
- (void) tearDown {
|
||||
[super tearDown];
|
||||
delete logger;
|
||||
}
|
||||
|
||||
- (void) testLogReleasingWaitingThreads {
|
||||
logger->LogReleasingWaitingThreads();
|
||||
[self assertLoggedOneDebugMessage];
|
||||
}
|
||||
|
||||
- (void) testLogIfMachError_ReleaseWaitingThreadsSignal {
|
||||
logger->LogIfMachError_ReleaseWaitingThreadsSignal(KERN_SUCCESS);
|
||||
[self assertLoggedNoMessages];
|
||||
}
|
||||
|
||||
- (void) testLogIfDroppedFrames_didNotDropFrames {
|
||||
logger->LogIfDroppedFrames(11256.0, 11256.0);
|
||||
[self assertLoggedNoMessages];
|
||||
}
|
||||
|
||||
- (void) testLogIfDroppedFrames_didDropFrames {
|
||||
logger->LogIfDroppedFrames(11256.0, 11768.0);
|
||||
[self assertLoggedOneDebugMessage];
|
||||
}
|
||||
|
||||
- (void) testLogNoSamplesReady {
|
||||
logger->LogNoSamplesReady(512, 1024, 512.0);
|
||||
[self assertLoggedOneDebugMessage];
|
||||
}
|
||||
|
||||
- (void) testLogExceptionStoppingIOProc_withoutErrorCode {
|
||||
// Set a test-only flag that keeps it from calling abort() after logging the error when the
|
||||
// tests are compiled with the debug configuration.
|
||||
logger->mContinueOnErrorLogged = true;
|
||||
|
||||
logger->LogExceptionStoppingIOProc("InputDeviceIOProc");
|
||||
[self assertLoggedOneErrorMessage];
|
||||
}
|
||||
|
||||
- (void) testLogExceptionStoppingIOProc_withErrorCode {
|
||||
// Set a test-only flag that keeps it from calling abort() after logging the error when the
|
||||
// tests are compiled with the debug configuration.
|
||||
logger->mContinueOnErrorLogged = true;
|
||||
|
||||
logger->LogExceptionStoppingIOProc("OutputDeviceIOProc", kAudioHardwareUnknownPropertyError);
|
||||
[self assertLoggedOneErrorMessage];
|
||||
}
|
||||
|
||||
- (void) testLogUnexpectedIOStateAfterStopping {
|
||||
logger->LogUnexpectedIOStateAfterStopping("OutputDeviceIOProc", 1);
|
||||
[self assertLoggedOneWarningMessage];
|
||||
}
|
||||
|
||||
- (void) testLogRingBufferUnavailable {
|
||||
logger->LogRingBufferUnavailable("OutputDeviceIOProc", false);
|
||||
[self assertLoggedOneWarningMessage];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Fetch_noError {
|
||||
logger->LogIfRingBufferError_Fetch(kCARingBufferError_OK);
|
||||
[self assertLoggedNoMessages];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Fetch_errorCPUOverload {
|
||||
logger->LogIfRingBufferError_Fetch(kCARingBufferError_CPUOverload);
|
||||
[self assertLoggedOneWarningMessage];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Fetch_errorTooMuch {
|
||||
// Set a test-only flag that keeps it from calling abort() after logging the error when the
|
||||
// tests are compiled with the debug configuration.
|
||||
logger->mContinueOnErrorLogged = true;
|
||||
|
||||
logger->LogIfRingBufferError_Fetch(kCARingBufferError_TooMuch);
|
||||
[self assertLoggedOneErrorMessage];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Store_noError {
|
||||
logger->LogIfRingBufferError_Store(kCARingBufferError_OK);
|
||||
[self assertLoggedNoMessages];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Store_errorCPUOverload {
|
||||
logger->LogIfRingBufferError_Store(kCARingBufferError_CPUOverload);
|
||||
[self assertLoggedOneWarningMessage];
|
||||
}
|
||||
|
||||
- (void) testLogIfRingBufferError_Store_errorTooMuch {
|
||||
// Set a test-only flag that keeps it from calling abort() after logging the error when the
|
||||
// tests are compiled with the debug configuration.
|
||||
logger->mContinueOnErrorLogged = true;
|
||||
|
||||
logger->LogIfRingBufferError_Store(kCARingBufferError_TooMuch);
|
||||
[self assertLoggedOneErrorMessage];
|
||||
}
|
||||
|
||||
- (void) waitForLoggingThread {
|
||||
// Wait for it to finish logging the messages.
|
||||
bool noMessagesLeft = logger->WaitUntilLoggerThreadIdle();
|
||||
XCTAssert(noMessagesLeft);
|
||||
}
|
||||
|
||||
- (void) assertLoggedNoMessages {
|
||||
[self waitForLoggingThread];
|
||||
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
|
||||
}
|
||||
|
||||
- (void) assertLoggedOneDebugMessage {
|
||||
[self waitForLoggingThread];
|
||||
XCTAssertEqual(1, logger->mNumDebugMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
|
||||
}
|
||||
|
||||
- (void) assertLoggedOneWarningMessage {
|
||||
[self waitForLoggingThread];
|
||||
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
|
||||
XCTAssertEqual(1, logger->mNumWarningMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumErrorMessagesLogged);
|
||||
}
|
||||
|
||||
- (void) assertLoggedOneErrorMessage {
|
||||
[self waitForLoggingThread];
|
||||
XCTAssertEqual(0, logger->mNumDebugMessagesLogged);
|
||||
XCTAssertEqual(0, logger->mNumWarningMessagesLogged);
|
||||
XCTAssertEqual(1, logger->mNumErrorMessagesLogged);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMPlayThroughTests.mm
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Unit Include
|
||||
#import "BGMPlayThrough.h"
|
||||
|
||||
// Local Includes
|
||||
#import "MockAudioDevice.h"
|
||||
#import "MockAudioObjects.h"
|
||||
|
||||
// BGM Includes
|
||||
#import "BGM_Types.h"
|
||||
#import "BGMAudioDevice.h"
|
||||
|
||||
// STL Includes
|
||||
#import <memory>
|
||||
|
||||
// System Includes
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
|
||||
@interface BGMPlayThroughTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation BGMPlayThroughTests {
|
||||
BGMAudioDevice inputDevice;
|
||||
BGMAudioDevice outputDevice;
|
||||
|
||||
// The unit tests use mock implementations of CAHALAudioObject and CAHALAudioDevice, which are
|
||||
// the superclasses of BGMAudioDevice. When BGMPlayThrough calls methods on inputDevice or
|
||||
// outputDevice, some of them will update these objects.
|
||||
std::shared_ptr<MockAudioDevice> mockInputDevice;
|
||||
std::shared_ptr<MockAudioDevice> mockOutputDevice;
|
||||
}
|
||||
|
||||
- (void) setUp {
|
||||
[super setUp];
|
||||
|
||||
// Set up the mocks.
|
||||
mockInputDevice = MockAudioObjects::CreateMockDevice(kBGMDeviceUID);
|
||||
mockOutputDevice = MockAudioObjects::CreateMockDevice("Mock Output Device");
|
||||
|
||||
inputDevice = BGMAudioDevice(mockInputDevice->GetObjectID());
|
||||
outputDevice = BGMAudioDevice(mockOutputDevice->GetObjectID());
|
||||
}
|
||||
|
||||
- (void) tearDown {
|
||||
[super tearDown];
|
||||
MockAudioObjects::DestroyMocks();
|
||||
}
|
||||
|
||||
- (void) testActivate {
|
||||
// Set the mock output device's sample rate and IO buffer size.
|
||||
outputDevice.SetNominalSampleRate(12345.0);
|
||||
outputDevice.SetIOBufferSize(123);
|
||||
|
||||
// Create an instance and activate it.
|
||||
BGMPlayThrough playThrough(inputDevice, outputDevice);
|
||||
playThrough.Activate();
|
||||
|
||||
// It should set the input device's sample rate and IO buffer size to match the output device.
|
||||
XCTAssertEqual(12345.0, inputDevice.GetNominalSampleRate());
|
||||
XCTAssertEqual(123, inputDevice.GetIOBufferSize());
|
||||
|
||||
// It should add the property listeners it needs.
|
||||
std::set<AudioObjectPropertySelector> expectedProperties {
|
||||
kAudioDevicePropertyDeviceIsRunning,
|
||||
kAudioDeviceProcessorOverload,
|
||||
kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanBGMApp
|
||||
};
|
||||
|
||||
XCTAssertEqual(expectedProperties, mockInputDevice->mPropertiesWithListeners);
|
||||
}
|
||||
|
||||
- (void) testDeactivate {
|
||||
BGMPlayThrough playThrough(inputDevice, outputDevice);
|
||||
|
||||
playThrough.Activate();
|
||||
playThrough.Deactivate();
|
||||
|
||||
// It should remove the property listeners added by Activate.
|
||||
XCTAssert(mockInputDevice->mPropertiesWithListeners.empty());
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioDevice.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "MockAudioDevice.h"
|
||||
|
||||
// BGM Includes
|
||||
#include "BGM_Types.h"
|
||||
|
||||
// STL Includes
|
||||
#include <functional>
|
||||
|
||||
|
||||
MockAudioDevice::MockAudioDevice(const std::string& inUID)
|
||||
:
|
||||
mUID(inUID),
|
||||
mNominalSampleRate(44100.0),
|
||||
mIOBufferSize(512),
|
||||
MockAudioObject(static_cast<AudioObjectID>(std::hash<std::string>{}(inUID)))
|
||||
{
|
||||
}
|
||||
|
||||
CACFString MockAudioDevice::GetPlayerBundleID() const
|
||||
{
|
||||
if(mUID != kBGMDeviceUID)
|
||||
{
|
||||
throw "Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID";
|
||||
}
|
||||
|
||||
return mPlayerBundleID;
|
||||
}
|
||||
|
||||
void MockAudioDevice::SetPlayerBundleID(const CACFString& inPlayerBundleID)
|
||||
{
|
||||
if(mUID != kBGMDeviceUID)
|
||||
{
|
||||
throw "Only BGMDevice has kAudioDeviceCustomPropertyMusicPlayerBundleID";
|
||||
}
|
||||
|
||||
mPlayerBundleID = inPlayerBundleID;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioObject.h
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef BGMAppUnitTests__MockAudioDevice
|
||||
#define BGMAppUnitTests__MockAudioDevice
|
||||
|
||||
// Superclass Includes
|
||||
#include "MockAudioObject.h"
|
||||
|
||||
// STL Includes
|
||||
#include <string>
|
||||
|
||||
|
||||
/*!
|
||||
* A mock audio device in our mock CoreAudio HAL. In the HAL's API class hierarchy, the base class
|
||||
* for audio devices, kAudioDeviceClassID, is the audio objects class, kAudioObjectClassID.
|
||||
*
|
||||
* The unit tests generally use instances of this class to verify the HAL is being queried correctly
|
||||
* and to control the responses that the code they're testing will receive from the mock HAL.
|
||||
*/
|
||||
class MockAudioDevice
|
||||
:
|
||||
public MockAudioObject
|
||||
{
|
||||
|
||||
public:
|
||||
MockAudioDevice(const std::string& inUID);
|
||||
|
||||
/*!
|
||||
* @return This device's music player bundle ID property.
|
||||
* @throws If this device isn't a mock of BGMDevice.
|
||||
*/
|
||||
CACFString GetPlayerBundleID() const;
|
||||
/*!
|
||||
* Set this device's music player bundle ID property.
|
||||
* @throws If this device isn't a mock of BGMDevice.
|
||||
*/
|
||||
void SetPlayerBundleID(const CACFString& inPlayerBundleID);
|
||||
|
||||
/*!
|
||||
* The device's UID. The UID is a persistent token used to identify a particular audio device
|
||||
* across boot sessions.
|
||||
*/
|
||||
const std::string mUID;
|
||||
Float64 mNominalSampleRate;
|
||||
UInt32 mIOBufferSize;
|
||||
|
||||
private:
|
||||
CACFString mPlayerBundleID { "" };
|
||||
|
||||
};
|
||||
|
||||
#endif /* BGMAppUnitTests__MockAudioDevice */
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioObject.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "MockAudioObject.h"
|
||||
|
||||
|
||||
MockAudioObject::MockAudioObject(AudioObjectID inAudioObjectID)
|
||||
:
|
||||
mAudioObjectID(inAudioObjectID)
|
||||
{
|
||||
}
|
||||
|
||||
AudioObjectID MockAudioObject::GetObjectID() const
|
||||
{
|
||||
return mAudioObjectID;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioObject.h
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef BGMAppUnitTests__MockAudioObject
|
||||
#define BGMAppUnitTests__MockAudioObject
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CACFString.h"
|
||||
|
||||
// STL Includes
|
||||
#include <set>
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
|
||||
/*!
|
||||
* The base class for mock audio objects in our mock CoreAudio HAL. Maps to kAudioObjectClassID
|
||||
* (AudioHardwareBase.h) in the HAL's API class hierarchy.
|
||||
*/
|
||||
class MockAudioObject
|
||||
{
|
||||
|
||||
public:
|
||||
MockAudioObject(AudioObjectID inAudioObjectID);
|
||||
virtual ~MockAudioObject() = default;
|
||||
|
||||
AudioObjectID GetObjectID() const;
|
||||
|
||||
/*!
|
||||
* The properties that callers have added listeners for (and haven't since removed). See
|
||||
* CAHALAudioObject::AddPropertyListener and CAHALAudioObject::RemovePropertyListener.
|
||||
*/
|
||||
std::set<AudioObjectPropertySelector> mPropertiesWithListeners;
|
||||
|
||||
private:
|
||||
AudioObjectID mAudioObjectID;
|
||||
|
||||
};
|
||||
|
||||
#endif /* BGMAppUnitTests__MockAudioObject */
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioObjects.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "MockAudioObjects.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CACFString.h"
|
||||
|
||||
|
||||
// static
|
||||
MockAudioObjects::MockDeviceMap MockAudioObjects::sDevices;
|
||||
|
||||
// static
|
||||
MockAudioObjects::MockDeviceMapByUID MockAudioObjects::sDevicesByUID;
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioDevice> MockAudioObjects::CreateMockDevice(const std::string& inUID)
|
||||
{
|
||||
std::shared_ptr<MockAudioDevice> mockDevice = std::make_shared<MockAudioDevice>(inUID);
|
||||
|
||||
sDevices.insert(MockDeviceMap::value_type(mockDevice->GetObjectID(), mockDevice));
|
||||
sDevicesByUID.insert(MockDeviceMapByUID::value_type(inUID, mockDevice));
|
||||
|
||||
return mockDevice;
|
||||
}
|
||||
|
||||
// static
|
||||
void MockAudioObjects::DestroyMocks()
|
||||
{
|
||||
sDevices.clear();
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioObject> MockAudioObjects::GetAudioObject(AudioObjectID inAudioObjectID)
|
||||
{
|
||||
auto device = GetAudioDeviceOrNull(inAudioObjectID);
|
||||
|
||||
if(device)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
// Devices are the only audio objects we currently mock.
|
||||
|
||||
// Tests have to create mocks for all of the audio objects they expect the code they test to
|
||||
// access. They should fail if it accesses any others.
|
||||
throw "Mock audio object not found.";
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(AudioObjectID inAudioObjectID)
|
||||
{
|
||||
auto device = GetAudioDeviceOrNull(inAudioObjectID);
|
||||
|
||||
if(device)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
|
||||
// Tests have to create mocks for all of the audio devices they expect the code they test to
|
||||
// access. They should fail if it accesses any others.
|
||||
throw "Mock audio device not found.";
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(CFStringRef inUID)
|
||||
{
|
||||
// Convert inUID to a std::string.
|
||||
UInt32 uidCStringLen = CACFString::GetStringByteLength(inUID) + 1;
|
||||
char uidCString[uidCStringLen];
|
||||
CACFString::GetCString(inUID, uidCString, uidCStringLen);
|
||||
std::string uid = std::string(uidCString);
|
||||
|
||||
return GetAudioDevice(uid);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioDevice> MockAudioObjects::GetAudioDevice(const std::string& inUID)
|
||||
{
|
||||
auto device = sDevicesByUID.find(inUID);
|
||||
|
||||
if(device != sDevicesByUID.end())
|
||||
{
|
||||
return device->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<MockAudioDevice>
|
||||
MockAudioObjects::GetAudioDeviceOrNull(AudioObjectID inAudioObjectID)
|
||||
{
|
||||
auto device = sDevices.find(inAudioObjectID);
|
||||
|
||||
if(device != sDevices.end())
|
||||
{
|
||||
return device->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// MockAudioObjects.h
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef BGMAppUnitTests__MockAudioObjects
|
||||
#define BGMAppUnitTests__MockAudioObjects
|
||||
|
||||
// Local Includes
|
||||
#include "MockAudioObject.h"
|
||||
#include "MockAudioDevice.h"
|
||||
|
||||
// STL Includes
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/CoreAudio.h>
|
||||
|
||||
|
||||
class MockAudioObjects
|
||||
{
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Create a mock audio device in the mock CoreAudio HAL.
|
||||
*
|
||||
* The mock device will then be accessible using GetAudioObject and GetAudioDevice. The
|
||||
* Mock_CAHAL* implementations will access the mock device when they query the mock HAL.
|
||||
*
|
||||
* Unit tests can check the mock device to verify the code they're testing has called the mocked
|
||||
* CAHAL classes correctly. They can also modify the mock device to control the Mock_CAHAL*
|
||||
* implementations, e.g. to have CAHALAudioDevice::IsAlive return false so the test can cover
|
||||
* the case where a device is being removed from the system.
|
||||
*
|
||||
* @param inUID The UID string to give the device. The UID is a persistent token used to
|
||||
* identify a particular audio device across boot sessions.
|
||||
* @return The mock device.
|
||||
*/
|
||||
static std::shared_ptr<MockAudioDevice> CreateMockDevice(const std::string& inUID);
|
||||
|
||||
/*!
|
||||
* Remove all mock audio objects from the mock HAL. (Currently, mock devices are the only mock
|
||||
* objects that can be created.)
|
||||
*/
|
||||
static void DestroyMocks();
|
||||
|
||||
/*! Get a mock audio object by its ID. */
|
||||
static std::shared_ptr<MockAudioObject> GetAudioObject(AudioObjectID inAudioObjectID);
|
||||
|
||||
/*! Get a mock audio device by its ID. */
|
||||
static std::shared_ptr<MockAudioDevice> GetAudioDevice(AudioObjectID inAudioDeviceID);
|
||||
/*! Get a mock audio device by its UID. */
|
||||
static std::shared_ptr<MockAudioDevice> GetAudioDevice(const std::string& inUID);
|
||||
/*! Get a mock audio device by its UID. */
|
||||
static std::shared_ptr<MockAudioDevice> GetAudioDevice(CFStringRef inUID);
|
||||
|
||||
private:
|
||||
typedef std::map<AudioObjectID, std::shared_ptr<MockAudioDevice>> MockDeviceMap;
|
||||
typedef std::map<std::string, std::shared_ptr<MockAudioDevice>> MockDeviceMapByUID;
|
||||
|
||||
static std::shared_ptr<MockAudioDevice> GetAudioDeviceOrNull(AudioObjectID inAudioDeviceID);
|
||||
|
||||
/*! Maps IDs to mocked audio devices. */
|
||||
static MockDeviceMap sDevices;
|
||||
/*! Maps UIDs (ID strings) to mocked audio devices. */
|
||||
static MockDeviceMapByUID sDevicesByUID;
|
||||
|
||||
};
|
||||
|
||||
#endif /* BGMAppUnitTests__MockAudioObjects */
|
||||
|
||||
@@ -0,0 +1,691 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// Mock_CAHALAudioDevice.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "CAHALAudioDevice.h"
|
||||
|
||||
// Local Includes
|
||||
#include "MockAudioDevice.h"
|
||||
#include "MockAudioObjects.h"
|
||||
|
||||
// BGM Includes
|
||||
#include "BGM_Types.h"
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CACFString.h"
|
||||
#include "CAHALAudioSystemObject.h"
|
||||
#include "CAPropertyAddress.h"
|
||||
|
||||
|
||||
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
CAHALAudioDevice::CAHALAudioDevice(AudioObjectID inObjectID)
|
||||
:
|
||||
CAHALAudioObject(inObjectID)
|
||||
{
|
||||
}
|
||||
|
||||
CAHALAudioDevice::CAHALAudioDevice(CFStringRef inUID)
|
||||
:
|
||||
CAHALAudioObject(CAHALAudioSystemObject().GetAudioDeviceForUID(inUID))
|
||||
{
|
||||
}
|
||||
|
||||
CAHALAudioDevice::~CAHALAudioDevice()
|
||||
{
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetCurrentVirtualFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const
|
||||
{
|
||||
ioNumberStreams = 1;
|
||||
CAPropertyAddress theAddress(kAudioStreamPropertyVirtualFormat);
|
||||
UInt32 theSize = sizeof(AudioStreamBasicDescription);
|
||||
GetPropertyData(theAddress, 0, NULL, theSize, outFormats);
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetIOBufferSize() const
|
||||
{
|
||||
return MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize;
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetIOBufferSize(UInt32 inBufferSize)
|
||||
{
|
||||
MockAudioObjects::GetAudioDevice(GetObjectID())->mIOBufferSize = inBufferSize;
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsAlive() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioDeviceIOProcID CAHALAudioDevice::CreateIOProcID(AudioDeviceIOProc inIOProc, void* inClientData)
|
||||
{
|
||||
return reinterpret_cast<AudioDeviceIOProcID>(0x99990000);
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::DestroyIOProcID(AudioDeviceIOProcID inIOProcID)
|
||||
{
|
||||
}
|
||||
|
||||
Float64 CAHALAudioDevice::GetNominalSampleRate() const
|
||||
{
|
||||
return MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate;
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetNominalSampleRate(Float64 inSampleRate)
|
||||
{
|
||||
MockAudioObjects::GetAudioDevice(GetObjectID())->mNominalSampleRate = inSampleRate;
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyDeviceUID() const
|
||||
{
|
||||
std::string uid = MockAudioObjects::GetAudioDevice(GetObjectID())->mUID;
|
||||
return CACFString(uid.c_str()).CopyCFString();
|
||||
}
|
||||
|
||||
#pragma mark Unimplemented Methods
|
||||
|
||||
bool CAHALAudioDevice::HasModelUID() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyModelUID() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyConfigurationApplicationBundleID() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFURLRef CAHALAudioDevice::CopyIconLocation() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetTransportType() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::CanBeDefaultDevice(bool inIsInput, bool inIsSystem) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasDevicePlugInStatus() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
OSStatus CAHALAudioDevice::GetDevicePlugInStatus() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsHidden() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
pid_t CAHALAudioDevice::GetHogModeOwner() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsHogModeSettable() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::TakeHogMode()
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::ReleaseHogMode()
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasPreferredStereoChannels(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetPreferredStereoChannels(bool inIsInput, UInt32& outLeft, UInt32& outRight) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, UInt32 inLeft, UInt32 inRight)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasPreferredChannelLayout(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetPreferredChannelLayout(bool inIsInput, AudioChannelLayout& outChannelLayout) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetPreferredStereoChannels(bool inIsInput, AudioChannelLayout& inChannelLayout)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberRelatedAudioDevices() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetRelatedAudioDevices(UInt32& ioNumberRelatedDevices, AudioObjectID* outRelatedDevices) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
AudioObjectID CAHALAudioDevice::GetRelatedAudioDeviceByIndex(UInt32 inIndex) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberStreams(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetStreams(bool inIsInput, UInt32& ioNumberStreams, AudioObjectID* outStreamList) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
AudioObjectID CAHALAudioDevice::GetStreamByIndex(bool inIsInput, UInt32 inIndex) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetTotalNumberChannels(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetCurrentPhysicalFormats(bool inIsInput, UInt32& ioNumberStreams, AudioStreamBasicDescription* outFormats) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsRunning() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsRunningSomewhere() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetLatency(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetSafetyOffset(bool inIsInput) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasClockDomain() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetClockDomain() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float64 CAHALAudioDevice::GetActualSampleRate() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberAvailableNominalSampleRateRanges() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetAvailableNominalSampleRateRanges(UInt32& ioNumberRanges, AudioValueRange* outRanges) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetAvailableNominalSampleRateRangeByIndex(UInt32 inIndex, Float64& outMinimum, Float64& outMaximum) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsValidNominalSampleRate(Float64 inSampleRate) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::IsIOBufferSizeSettable() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::UsesVariableIOBufferSizes() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetMaximumVariableIOBufferSize() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasIOBufferSizeRange() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetIOBufferSizeRange(UInt32& outMinimum, UInt32& outMaximum) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::StartIOProc(AudioDeviceIOProcID inIOProcID)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::StartIOProcAtTime(AudioDeviceIOProcID inIOProcID, AudioTimeStamp& ioStartTime, bool inIsInput, bool inIgnoreHardware)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::StopIOProc(AudioDeviceIOProcID inIOProcID)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, bool* outStreamUsage) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetIOProcStreamUsage(AudioDeviceIOProcID inIOProcID, bool inIsInput, const bool* inStreamUsage)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetIOCycleUsage() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetIOCycleUsage(Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetCurrentTime(AudioTimeStamp& outTime)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::TranslateTime(const AudioTimeStamp& inTime, AudioTimeStamp& outTime)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetNearestStartTime(AudioTimeStamp& ioTime, bool inIsInput, bool inIgnoreHardware)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::VolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasSubVolumeControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::SubVolumeControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetSubVolumeControlScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetSubVolumeControlDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetSubVolumeControlScalarForDecibelValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetSubVolumeControlDecibelForScalarValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::MuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::GetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasSoloControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::SoloControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::GetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetSoloControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasStereoPanControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::StereoPanControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
Float32 CAHALAudioDevice::GetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetStereoPanControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, Float32 inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetStereoPanControlChannels(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& outLeftChannel, UInt32& outRightChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasJackControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::GetJackControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasSubMuteControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::SubMuteControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::GetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetSubMuteControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasiSubOwnerControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::iSubOwnerControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::GetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetiSubOwnerControlValue(AudioObjectPropertyScope inScope, UInt32 inChannel, bool inValue)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasDataSourceControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::DataSourceControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetCurrentDataSourceID(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetCurrentDataSourceByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetAvailableDataSources(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberSources, UInt32* outSources) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetAvailableDataSourceByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyDataSourceNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasDataDestinationControl(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::DataDestinationControlIsSettable(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetCurrentDataDestinationID(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetCurrentDataDestinationByID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetAvailableDataDestinations(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32& ioNumberDestinations, UInt32* outDestinations) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetAvailableDataDestinationByIndex(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inIndex) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyDataDestinationNameForID(AudioObjectPropertyScope inScope, UInt32 inChannel, UInt32 inID) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::HasClockSourceControl() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
bool CAHALAudioDevice::ClockSourceControlIsSettable() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetCurrentClockSourceID() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::SetCurrentClockSourceByID(UInt32 inID)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetNumberAvailableClockSources() const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioDevice::GetAvailableClockSources(UInt32& ioNumberSources, UInt32* outSources) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetAvailableClockSourceByIndex(UInt32 inIndex) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
CFStringRef CAHALAudioDevice::CopyClockSourceNameForID(UInt32 inID) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioDevice::GetClockSourceKindForID(UInt32 inID) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
+80
-28
@@ -17,25 +17,24 @@
|
||||
// Mock_CAHALAudioObject.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2016 Kyle Neideck
|
||||
// Copyright © 2016, 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self include
|
||||
// Self Include
|
||||
#include "CAHALAudioObject.h"
|
||||
|
||||
// Local Includes
|
||||
#include "MockAudioObjects.h"
|
||||
|
||||
// BGM Includes
|
||||
#include "BGM_Types.h"
|
||||
|
||||
// System includes
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
// PublicUtility Includes
|
||||
#include "CACFString.h"
|
||||
|
||||
|
||||
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// The value of the music player bundle ID property. Tests should set this back to "" when they finish. (Has
|
||||
// to be static because we can't add to the real class's interface.)
|
||||
static CFStringRef playerBundleID = CFSTR("");
|
||||
|
||||
CAHALAudioObject::CAHALAudioObject(AudioObjectID inObjectID)
|
||||
:
|
||||
mObjectID(inObjectID)
|
||||
@@ -53,21 +52,89 @@ AudioObjectID CAHALAudioObject::GetObjectID() const
|
||||
|
||||
void CAHALAudioObject::GetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32& ioDataSize, void* outData) const
|
||||
{
|
||||
if(inAddress.mSelector == kAudioDeviceCustomPropertyMusicPlayerBundleID)
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
*reinterpret_cast<CFStringRef*>(outData) = playerBundleID;
|
||||
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
|
||||
*reinterpret_cast<CFStringRef*>(outData) =
|
||||
MockAudioObjects::GetAudioDevice(GetObjectID())->
|
||||
GetPlayerBundleID().CopyCFString();
|
||||
break;
|
||||
|
||||
case kAudioDevicePropertyStreams:
|
||||
reinterpret_cast<AudioObjectID*>(outData)[0] = 1;
|
||||
if(inAddress.mScope == kAudioObjectPropertyScopeGlobal)
|
||||
{
|
||||
reinterpret_cast<AudioObjectID*>(outData)[1] = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case kAudioDevicePropertyBufferFrameSize:
|
||||
*reinterpret_cast<UInt32*>(outData) = 512;
|
||||
break;
|
||||
|
||||
case kAudioDevicePropertyDeviceIsAlive:
|
||||
*reinterpret_cast<UInt32*>(outData) = 1;
|
||||
break;
|
||||
|
||||
case kAudioStreamPropertyVirtualFormat:
|
||||
{
|
||||
AudioStreamBasicDescription* outASBD =
|
||||
reinterpret_cast<AudioStreamBasicDescription*>(outData);
|
||||
outASBD->mSampleRate = 44100.0;
|
||||
outASBD->mFormatID = kAudioFormatLinearPCM;
|
||||
outASBD->mFormatFlags =
|
||||
kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
|
||||
outASBD->mBytesPerPacket = 8;
|
||||
outASBD->mFramesPerPacket = 1;
|
||||
outASBD->mBytesPerFrame = 8;
|
||||
outASBD->mChannelsPerFrame = 2;
|
||||
outASBD->mBitsPerChannel = 32;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
}
|
||||
|
||||
void CAHALAudioObject::SetPropertyData(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData)
|
||||
{
|
||||
if(inAddress.mSelector == kAudioDeviceCustomPropertyMusicPlayerBundleID)
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
playerBundleID = *reinterpret_cast<const CFStringRef*>(inData);
|
||||
case kAudioDeviceCustomPropertyMusicPlayerBundleID:
|
||||
MockAudioObjects::GetAudioDevice(GetObjectID())->SetPlayerBundleID(
|
||||
CACFString(*reinterpret_cast<const CFStringRef*>(inData), false));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Unimplemented methods
|
||||
UInt32 CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
|
||||
{
|
||||
switch(inAddress.mSelector)
|
||||
{
|
||||
case kAudioDevicePropertyStreams:
|
||||
return (inAddress.mScope == kAudioObjectPropertyScopeGlobal ? 2 : 1) *
|
||||
sizeof(AudioObjectID);
|
||||
default:
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
}
|
||||
|
||||
void CAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
|
||||
{
|
||||
MockAudioObjects::GetAudioObject(GetObjectID())->
|
||||
mPropertiesWithListeners.insert(inAddress.mSelector);
|
||||
}
|
||||
|
||||
void CAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
|
||||
{
|
||||
MockAudioObjects::GetAudioObject(GetObjectID())->
|
||||
mPropertiesWithListeners.erase(inAddress.mSelector);
|
||||
}
|
||||
|
||||
#pragma mark Unimplemented Methods
|
||||
|
||||
void CAHALAudioObject::SetObjectID(AudioObjectID inObjectID)
|
||||
{
|
||||
@@ -144,18 +211,3 @@ bool CAHALAudioObject::IsPropertySettable(const AudioObjectPropertyAddress& inAd
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
UInt32 CAHALAudioObject::GetPropertyDataSize(const AudioObjectPropertyAddress& inAddress, UInt32 inQualifierDataSize, const void* inQualifierData) const
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioObject::AddPropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
void CAHALAudioObject::RemovePropertyListener(const AudioObjectPropertyAddress& inAddress, AudioObjectPropertyListenerProc inListenerProc, void* inClientData)
|
||||
{
|
||||
Throw(new CAException(kAudio_UnimplementedError));
|
||||
}
|
||||
|
||||
+12
-8
@@ -17,12 +17,18 @@
|
||||
// Mock_CAHALAudioSystemObject.cpp
|
||||
// BGMAppUnitTests
|
||||
//
|
||||
// Copyright © 2017 Kyle Neideck
|
||||
// Copyright © 2017, 2020 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self include
|
||||
#include "CAHALAudioSystemObject.h"
|
||||
|
||||
// BGM Includes
|
||||
#include "BGM_Types.h"
|
||||
|
||||
// Local Includes
|
||||
#include "MockAudioObjects.h"
|
||||
|
||||
|
||||
CAHALAudioSystemObject::CAHALAudioSystemObject()
|
||||
:
|
||||
@@ -36,19 +42,17 @@ CAHALAudioSystemObject::~CAHALAudioSystemObject()
|
||||
|
||||
AudioObjectID CAHALAudioSystemObject::GetAudioDeviceForUID(CFStringRef inUID) const
|
||||
{
|
||||
AudioObjectID id = kAudioObjectUnknown;
|
||||
auto device = MockAudioObjects::GetAudioDevice(inUID);
|
||||
|
||||
// Generate a deterministic and random-ish ID from the UID string. Ideally we would ensure the
|
||||
// IDs are unique, but this is probably fine.
|
||||
for(int i = 0; i < CFStringGetLength(inUID); i++)
|
||||
if(device)
|
||||
{
|
||||
id += 37 * CFStringGetCharacterAtIndex(inUID, i);
|
||||
return device->GetObjectID();
|
||||
}
|
||||
|
||||
return id;
|
||||
return kAudioObjectUnknown;
|
||||
}
|
||||
|
||||
#pragma mark Unimplemented methods
|
||||
#pragma mark Unimplemented Methods
|
||||
|
||||
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
// 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/>.
|
||||
|
||||
// Original licence at the end of this file.
|
||||
|
||||
//
|
||||
// BGMThreadSafetyAnalysis.h
|
||||
// PublicUtility
|
||||
//
|
||||
// © Copyright 2007-2020, The Clang Team
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
// Macros that wrap Clang's attributes for statically checking concurrency properties. From
|
||||
// <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutexheader>.
|
||||
//
|
||||
|
||||
|
||||
#ifndef PublicUtility__BGMThreadSafetyAnalysis
|
||||
#define PublicUtility__BGMThreadSafetyAnalysis
|
||||
|
||||
// Enable thread safety attributes only with clang.
|
||||
// The attributes can be safely erased when compiling with other compilers.
|
||||
#if defined(__clang__) && (!defined(SWIG))
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
|
||||
#else
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
|
||||
#endif
|
||||
|
||||
#define CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
|
||||
|
||||
#define SCOPED_CAPABILITY \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
|
||||
|
||||
#define GUARDED_BY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
|
||||
|
||||
#define PT_GUARDED_BY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
|
||||
|
||||
#define ACQUIRED_BEFORE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRED_AFTER(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
|
||||
|
||||
#define REQUIRES(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
|
||||
|
||||
#define REQUIRES_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
|
||||
|
||||
#define ACQUIRE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define RELEASE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
|
||||
|
||||
#define RELEASE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define TRY_ACQUIRE(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
|
||||
|
||||
#define TRY_ACQUIRE_SHARED(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
|
||||
|
||||
#define EXCLUDES(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
|
||||
|
||||
#define ASSERT_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
|
||||
|
||||
#define ASSERT_SHARED_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
|
||||
|
||||
#define RETURN_CAPABILITY(x) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
|
||||
|
||||
#define NO_THREAD_SAFETY_ANALYSIS \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
|
||||
|
||||
#endif /* PublicUtility__BGMThreadSafetyAnalysis */
|
||||
|
||||
/*
|
||||
This file is derived from "mutex.h" from the Clang documentation at
|
||||
<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>. This is the original license of "mutex.h".
|
||||
|
||||
==============================================================================
|
||||
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
|
||||
==============================================================================
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
---- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
==============================================================================
|
||||
Software from third parties included in the LLVM Project:
|
||||
==============================================================================
|
||||
The LLVM Project contains third party software which is under different license
|
||||
terms. All such code will be identified clearly using at least one of two
|
||||
mechanisms:
|
||||
1) It will be in a separate directory tree with its own `LICENSE.txt` or
|
||||
`LICENSE` file at the top containing the specific license and restrictions
|
||||
which apply to that software, or
|
||||
2) It will contain specific license and restriction terms at the top of every
|
||||
file.
|
||||
|
||||
==============================================================================
|
||||
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
|
||||
==============================================================================
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2007-2019 University of Illinois at Urbana-Champaign.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
|
||||
LLVM Team
|
||||
|
||||
University of Illinois at Urbana-Champaign
|
||||
|
||||
http://llvm.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal with
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of the LLVM Team, University of Illinois at
|
||||
Urbana-Champaign, nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this Software without specific
|
||||
prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.3.0</string>
|
||||
<string>0.4.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2016, 2017 Background Music contributors</string>
|
||||
<string>Copyright © 2016-2024 Background Music contributors</string>
|
||||
<key>XPCService</key>
|
||||
<dict>
|
||||
<key>ServiceType</key>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# post_install.sh
|
||||
# BGMXPCHelper
|
||||
#
|
||||
# Copyright © 2016-2018 Kyle Neideck
|
||||
# Copyright © 2016-2020 Kyle Neideck
|
||||
#
|
||||
# Installs BGMXPCHelper's launchd plist file and "bootstraps" (registers/enables) it with launchd.
|
||||
#
|
||||
@@ -68,7 +68,13 @@ fi
|
||||
# If DEPLOYMENT_POSTPROCESSING is true, xcodebuild calls this script even if you're just building
|
||||
# (and not also installing). I'm not sure why, as we have the "run script only when installing"
|
||||
# option enabled.
|
||||
if ! [[ -z ${ACTION} ]] && [[ "${ACTION}" != "install" ]]; then
|
||||
#
|
||||
# REAL_ACTION is a workaround for xcodebuild setting ACTION to "install" even if we're actually
|
||||
# making an archive.
|
||||
#
|
||||
# TODO: Archiving BGMXPCHelper from Xcode instead of using build_and_install.sh still fails.
|
||||
if ( ! [[ -z ${ACTION} ]] && [[ "${ACTION}" != "install" ]] ) || \
|
||||
( ! [[ -z ${REAL_ACTION} ]] && [[ "${REAL_ACTION}" == "archive" ]] ); then
|
||||
echo "$0 should only be called during an install. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -71,7 +71,7 @@ check_dir() {
|
||||
pushd . > /dev/null
|
||||
|
||||
# Normalize the path and follow symlinks.
|
||||
REAL_PATH=$(python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$1")
|
||||
REAL_PATH=$(cd "$1" && pwd -P)
|
||||
cd "${REAL_PATH}"
|
||||
|
||||
DIR_IS_SAFE=0
|
||||
@@ -84,7 +84,7 @@ check_dir() {
|
||||
[[ $((0$(stat -f '%Lp' .) & 0022)) -eq 0 ]]; do
|
||||
# ...go upwards until we reach the root directory.
|
||||
cd ..
|
||||
if [[ "${PWD}" == / ]]; then
|
||||
if [[ "${PWD}" -ef / ]]; then
|
||||
DIR_IS_SAFE=1
|
||||
break
|
||||
fi
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
dispatch_semaphore_signal(replySemaphore);
|
||||
} forUISoundsDevice:NO];
|
||||
|
||||
// Very long timeout to make it less likely to fail on Travis CI when there's high contention.
|
||||
// Very long timeout to make it less likely to fail in CI builds when there's high contention.
|
||||
if (0 != dispatch_semaphore_wait(replySemaphore, dispatch_time(DISPATCH_TIME_NOW, 5 * 60 * NSEC_PER_SEC))) {
|
||||
XCTFail(@"Timed out waiting for BGMXPCHelper");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// BGMDebugLogging.c
|
||||
// PublicUtility
|
||||
//
|
||||
// Copyright © 2020, 2024 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "BGMDebugLogging.h"
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
// It's probably not ideal to use a global variable for this, but it's a lot easier.
|
||||
#if DEBUG || CoreAudio_Debug
|
||||
// Enable debug logging by default in debug builds.
|
||||
int gDebugLoggingIsEnabled = 1;
|
||||
#else
|
||||
int gDebugLoggingIsEnabled = 0;
|
||||
#endif
|
||||
|
||||
// We don't bother synchronising accesses of gDebugLoggingIsEnabled because it isn't really
|
||||
// necessary and would complicate code that accesses it on realtime threads.
|
||||
int BGMDebugLoggingIsEnabled(void)
|
||||
{
|
||||
return gDebugLoggingIsEnabled;
|
||||
}
|
||||
|
||||
void BGMSetDebugLoggingEnabled(int inEnabled)
|
||||
{
|
||||
gDebugLoggingIsEnabled = inEnabled;
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// This file is part of Background Music.
|
||||
//
|
||||
// Background Music is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// Background Music is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//
|
||||
// BGMDebugLogging.h
|
||||
// PublicUtility
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
// Functions to globally enable/disable debug logging, i.e. more detailed logging to help diagnose
|
||||
// bugs. If debug logging is enabled, the DebugMsg macro from CADebugMacros.h (and possibly others)
|
||||
// will log messages. If not, it won't do anything.
|
||||
//
|
||||
// If the preprocessor macro CoreAudio_UseSysLog is true, which is currently the case for all build
|
||||
// variants (see BGMApp/BGMApp.xcodeproj/project.pbxproj and
|
||||
// BGMDriver/BGMDriver.xcodeproj/project.pbxproj), those messages will be logged using syslog and
|
||||
// can be read using Console.app. Try searching for "background music", "bgm" or "coreaudiod".
|
||||
//
|
||||
// Debug logging is enabled by default in debug builds, but in release builds you have to enable it
|
||||
// by option-clicking the status bar icon and then checking the Debug Logging menu item. Enabling
|
||||
// debug logging probably won't cause glitches, but we don't try to guarantee that and it's not
|
||||
// well tested.
|
||||
//
|
||||
|
||||
#ifndef PublicUtility__BGMDebugLogging
|
||||
#define PublicUtility__BGMDebugLogging
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
/*!
|
||||
* @return Non-zero if debug logging is globally enabled. (Probably -- it's not synchronised.)
|
||||
* Real-time safe.
|
||||
*/
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
#endif
|
||||
int BGMDebugLoggingIsEnabled(void);
|
||||
|
||||
/*!
|
||||
* @param inEnabled Non-zero to globally enable debug logging, zero to disable it. The change might
|
||||
* not be visible to other threads immediately.
|
||||
*/
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
#endif
|
||||
void BGMSetDebugLoggingEnabled(int inEnabled);
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* PublicUtility__BGMDebugLogging */
|
||||
|
||||
@@ -59,13 +59,15 @@ full barrier.
|
||||
#if TARGET_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <intrin.h>
|
||||
#pragma intrinsic(_InterlockedOr)
|
||||
#pragma intrinsic(_InterlockedOr)
|
||||
#pragma intrinsic(_InterlockedAnd)
|
||||
#else
|
||||
#include <CoreFoundation/CFBase.h>
|
||||
#include <libkern/OSAtomic.h>
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
inline void CAMemoryBarrier()
|
||||
{
|
||||
#if TARGET_OS_WIN32
|
||||
@@ -196,6 +198,8 @@ inline bool CAAtomicTestAndSetBarrier(int bitToSet, void* theAddress)
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
// int32_t flavors -- for C++ only since we can't overload in C
|
||||
// CFBase.h defines SInt32 as signed int which is similar to int32_t. If CFBase.h is included, then
|
||||
// this will generate redefinition error. But on Mac, CFBase.h, still includes MacTypes.h where
|
||||
@@ -243,6 +247,9 @@ inline int32_t CAAtomicDecrement32Barrier(volatile int32_t* theValue)
|
||||
}
|
||||
#endif // __cplusplus && !__LP64__
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
|
||||
#if __LP64__
|
||||
inline bool CAAtomicCompareAndSwap64Barrier( int64_t __oldValue, int64_t __newValue, volatile int64_t *__theValue )
|
||||
{
|
||||
@@ -259,6 +266,8 @@ inline bool CAAtomicCompareAndSwapPtrBarrier(void *__oldValue, void *__newValue,
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
/* Spinlocks. These use memory barriers as required to synchronize access to shared
|
||||
* memory protected by the lock. The lock operation spins, but employs various strategies
|
||||
* to back off if the lock is held, making it immune to most priority-inversion livelocks.
|
||||
@@ -273,6 +282,9 @@ bool CASpinLockTry( volatile CASpinLock *__lock );
|
||||
void CASpinLockLock( volatile CASpinLock *__lock );
|
||||
void CASpinLockUnlock( volatile CASpinLock *__lock );
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
|
||||
inline void CASpinLockLock( volatile CASpinLock *__lock )
|
||||
{
|
||||
#if TARGET_OS_MAC
|
||||
@@ -301,5 +313,6 @@ inline bool CASpinLockTry( volatile CASpinLock *__lock )
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif // __CAAtomic_h__
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// CADebugMacros.cpp
|
||||
// PublicUtility
|
||||
//
|
||||
// Copyright (C) 2014 Apple Inc. All Rights Reserved.
|
||||
// Copyright © 2016, 2017, 2020 Kyle Neideck
|
||||
//
|
||||
// Original license header follows.
|
||||
//
|
||||
|
||||
/*
|
||||
File: CADebugMacros.cpp
|
||||
Abstract: CADebugMacros.h
|
||||
@@ -46,7 +71,6 @@
|
||||
*/
|
||||
#include "CADebugMacros.h"
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#if TARGET_API_MAC_OSX
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
@@ -67,14 +91,12 @@ void LogError(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
// BGM edit: vprintf leaves args in an undefined state, which can cause a crash in
|
||||
// vsyslog. Also added CADebuggerStop(). Original code commented out below.
|
||||
//#if DEBUG
|
||||
// vprintf(fmt, args);
|
||||
//#endif
|
||||
//#if TARGET_API_MAC_OSX
|
||||
// vsyslog(LOG_ERR, fmt, args);
|
||||
//#endif
|
||||
vLogError(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void vLogError(const char *fmt, va_list args)
|
||||
{
|
||||
#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog
|
||||
printf("[ERROR] ");
|
||||
vprintf(fmt, args);
|
||||
@@ -82,25 +104,22 @@ void LogError(const char *fmt, ...)
|
||||
#else
|
||||
vsyslog(LOG_ERR, fmt, args);
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
CADebuggerStop();
|
||||
#endif
|
||||
// BGM edit end
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void LogWarning(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
// BGM edit: vprintf leaves args in an undefined state, which can cause a crash in
|
||||
// vsyslog. Also added CADebuggerStop(). Original code commented out below.
|
||||
//#if DEBUG
|
||||
// vprintf(fmt, args);
|
||||
//#endif
|
||||
//#if TARGET_API_MAC_OSX
|
||||
// vsyslog(LOG_WARNING, fmt, args);
|
||||
//#endif
|
||||
vLogWarning(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void vLogWarning(const char *fmt, va_list args)
|
||||
{
|
||||
#if (DEBUG || !TARGET_API_MAC_OSX) && !CoreAudio_UseSysLog
|
||||
printf("[WARNING] ");
|
||||
vprintf(fmt, args);
|
||||
@@ -108,9 +127,8 @@ void LogWarning(const char *fmt, ...)
|
||||
#else
|
||||
vsyslog(LOG_WARNING, fmt, args);
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
//CADebuggerStop(); // TODO: Add a toggle for this to the project file (under "Preprocessor Macros"). Default to off.
|
||||
#endif
|
||||
// BGM edit end
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// CADebugMacros.h
|
||||
// PublicUtility
|
||||
//
|
||||
// Copyright (C) 2014 Apple Inc. All Rights Reserved.
|
||||
// Copyright © 2016, 2020 Kyle Neideck
|
||||
//
|
||||
// Original license header follows.
|
||||
//
|
||||
|
||||
/*
|
||||
File: CADebugMacros.h
|
||||
Abstract: Part of CoreAudio Utility Classes
|
||||
@@ -57,6 +82,10 @@
|
||||
#include "CoreAudioTypes.h"
|
||||
#endif
|
||||
|
||||
#include "CADebugPrintf.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
//=============================================================================
|
||||
// CADebugMacros
|
||||
//=============================================================================
|
||||
@@ -92,42 +121,41 @@
|
||||
|
||||
#pragma mark Basic Definitions
|
||||
|
||||
// basic debugging print routines
|
||||
#if TARGET_OS_MAC && !TARGET_API_MAC_CARBON
|
||||
extern void DebugStr(const unsigned char* debuggerMsg);
|
||||
#define DebugMessage(msg) DebugStr("\p"msg)
|
||||
#define DebugMessageN1(msg, N1)
|
||||
#define DebugMessageN2(msg, N1, N2)
|
||||
#define DebugMessageN3(msg, N1, N2, N3)
|
||||
#else
|
||||
#if (CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)
|
||||
#define FlushRtn ,fflush(DebugPrintfFile)
|
||||
#else
|
||||
#define FlushRtn
|
||||
#endif
|
||||
|
||||
#if CoreAudio_ThreadStampMessages
|
||||
#include <pthread.h>
|
||||
#include "CAHostTimeBase.h"
|
||||
#if TARGET_RT_64_BIT
|
||||
#define DebugPrintfThreadIDFormat "%16p"
|
||||
#else
|
||||
#define DebugPrintfThreadIDFormat "%8p"
|
||||
#endif
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " DebugPrintfThreadIDFormat " " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn
|
||||
#elif CoreAudio_TimeStampMessages
|
||||
#include "CAHostTimeBase.h"
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn
|
||||
#else
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG || CoreAudio_Debug
|
||||
// can be used to break into debugger immediately, also see CADebugger
|
||||
#define BusError() { long* p=NULL; *p=0; }
|
||||
|
||||
// basic debugging print routines
|
||||
#if TARGET_OS_MAC && !TARGET_API_MAC_CARBON
|
||||
extern void DebugStr(const unsigned char* debuggerMsg);
|
||||
#define DebugMessage(msg) DebugStr("\p"msg)
|
||||
#define DebugMessageN1(msg, N1)
|
||||
#define DebugMessageN2(msg, N1, N2)
|
||||
#define DebugMessageN3(msg, N1, N2, N3)
|
||||
#else
|
||||
#include "CADebugPrintf.h"
|
||||
|
||||
#if (CoreAudio_FlushDebugMessages && !CoreAudio_UseSysLog) || defined(CoreAudio_UseSideFile)
|
||||
#define FlushRtn ,fflush(DebugPrintfFile)
|
||||
#else
|
||||
#define FlushRtn
|
||||
#endif
|
||||
|
||||
#if CoreAudio_ThreadStampMessages
|
||||
#include <pthread.h>
|
||||
#include "CAHostTimeBase.h"
|
||||
#if TARGET_RT_64_BIT
|
||||
#define DebugPrintfThreadIDFormat "%16p"
|
||||
#else
|
||||
#define DebugPrintfThreadIDFormat "%8p"
|
||||
#endif
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " DebugPrintfThreadIDFormat " " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), pthread_self(), ## __VA_ARGS__) FlushRtn
|
||||
#elif CoreAudio_TimeStampMessages
|
||||
#include "CAHostTimeBase.h"
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf("%17qd: " inFormat, CAHostTimeBase::GetCurrentTimeInNanos(), ## __VA_ARGS__) FlushRtn
|
||||
#else
|
||||
#define DebugMsg(inFormat, ...) DebugPrintf(inFormat, ## __VA_ARGS__) FlushRtn
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void DebugPrint(const char *fmt, ...); // can be used like printf
|
||||
#ifndef DEBUGPRINT
|
||||
#define DEBUGPRINT(msg) DebugPrint msg // have to double-parenthesize arglist (see Debugging.h)
|
||||
@@ -172,7 +200,7 @@
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define DebugMsg(inFormat, ...)
|
||||
|
||||
#ifndef DEBUGPRINT
|
||||
#define DEBUGPRINT(msg)
|
||||
#endif
|
||||
@@ -194,9 +222,11 @@
|
||||
#define DebugMessageN8(msg, N1, N2, N3, N4, N5, N6, N7, N8) DebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8)
|
||||
#define DebugMessageN9(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9) DebugMsg(msg, N1, N2, N3, N4, N5, N6, N7, N8, N9)
|
||||
|
||||
// BGM edit: Added __printflike.
|
||||
// BGM edit: Added __printflike and va_list versions.
|
||||
void LogError(const char *fmt, ...) __printflike(1, 2); // writes to syslog (and stderr if debugging)
|
||||
void vLogError(const char *fmt, va_list args);
|
||||
void LogWarning(const char *fmt, ...) __printflike(1, 2); // writes to syslog (and stderr if debugging)
|
||||
void vLogWarning(const char *fmt, va_list args);
|
||||
|
||||
#define NO_ACTION (void)0
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user