BackgroundMusic/SharedSource/BGM_Utils.h
Kyle Neideck 4c0c656538
Store the preferred devices list in User Defaults.
BGMApp has to set BGMDevice, and often also the Null Device for a short
time, as the systemwide default audio device, which makes CoreAudio put
them in the preferred devices list in its Plist file. And since the list
is limited to three devices, it only gives us one or two usable ones.
Ideally, CoreAudio just wouldn't add our devices to its list, but I
don't think we can prevent that.

As a partial workaround, we now store our own copy of the preferred
devices list without our devices, which BGMApp can use to figure out
which devices were pushed out of CoreAudio's list by our devices.

This doesn't fix the problem entirely because our devices still take up
room in CoreAudio's list when BGMApp is closed, but I think that would
be harder to solve.

See #167.

Also:
 - Handle setting the initial output device in BGMPreferredOutputDevices
   instead of BGMAudioDeviceManager.
 - Fix a crash in BGMOutputVolumeMenuItem::dealloc caused by using
   dispatch_sync to dispatch to the main queue while running on the main
   queue.
 - Fix a crash in BGMPreferredOutputDevices if
   /Library/Preferences/Audio/com.apple.audio.SystemSettings.plist
   doesn't exist.
 - Add Swinsian to the list of music players in the README. (I must have
   forgotten to do that when I added support for it.)
2018-10-28 17:08:47 +11:00

220 lines
7.8 KiB
Objective-C

// 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/>.
//
// BGM_Utils.h
// SharedSource
//
// Copyright © 2016-2018 Kyle Neideck
//
#ifndef SharedSource__BGM_Utils
#define SharedSource__BGM_Utils
// PublicUtility Includes
#include "CADebugMacros.h"
#if defined(__cplusplus)
#include "CAException.h"
// STL Includes
#include <functional>
#endif /* defined(__cplusplus) */
// System Includes
#include <mach/error.h>
#include <dispatch/dispatch.h>
#pragma mark Macros
// The Assert macro from CADebugMacros with support for format strings added.
#define BGMAssert(inCondition, inMessage, ...) \
if(!(inCondition)) \
{ \
DebugMsg(inMessage, ## __VA_ARGS__); \
__ASSERT_STOP; \
}
#define BGMAssertNonNull(expression) \
BGMAssertNonNull2((expression), #expression)
#define BGMAssertNonNull2(expression, expressionStr) \
BGMAssert((expression), \
"%s:%d:%s: '%s' is null", \
__FILE__, \
__LINE__, \
__FUNCTION__, \
expressionStr);
// Used to give the first 3 arguments of BGM_Utils::LogAndSwallowExceptions and
// BGM_Utils::LogUnexpectedExceptions (and probably others in future). Mainly so we can call those
// functions directly instead of using the macro wrappers.
#define BGMDbgArgs __FILE__, __LINE__, __FUNCTION__
#pragma mark Objective-C Macros
#if defined(__OBJC__)
#if __has_feature(objc_generics)
// This trick is from https://gist.github.com/robb/d55b72d62d32deaee5fa
@interface BGMNonNullCastHelper<__covariant T>
- (nonnull T) asNonNull;
@end
// Explicitly casts expressions from nullable to non-null. Only works with expressions that
// evaluate to Objective-C objects. Use BGM_Utils::NN for other types.
//
// TODO: Replace existing non-null casts with this.
#define BGMNN(expression) ({ \
__typeof((expression)) value = (expression); \
BGMAssertNonNull2(value, #expression); \
BGMNonNullCastHelper<__typeof((expression))>* helper; \
(__typeof(helper.asNonNull) __nonnull)value; \
})
#else /* __has_feature(objc_generics) */
#define BGMNN(expression) ({ \
id value = (expression); \
BGMAssertNonNull2(value, #expression); \
value; \
})
#endif /* __has_feature(objc_generics) */
#endif /* defined(__OBJC__) */
#pragma mark C++ Macros
#if defined(__cplusplus)
#define BGMLogException(exception) \
BGM_Utils::LogException(__FILE__, __LINE__, __FUNCTION__, exception)
#define BGMLogExceptionIn(callerName, exception) \
BGM_Utils::LogException(__FILE__, __LINE__, callerName, exception)
#define BGMLogAndSwallowExceptions(callerName, function) \
BGM_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, function)
#define BGMLogAndSwallowExceptionsMsg(callerName, message, function) \
BGM_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, message, function)
#define BGMLogUnexpectedException() \
BGM_Utils::LogUnexpectedException(__FILE__, __LINE__, __FUNCTION__)
#define BGMLogUnexpectedExceptionIn(callerName) \
BGM_Utils::LogUnexpectedException(__FILE__, __LINE__, callerName)
#define BGMLogUnexpectedExceptions(callerName, function) \
BGM_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, function)
#define BGMLogUnexpectedExceptionsMsg(callerName, message, function) \
BGM_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, message, function)
#endif /* defined(__cplusplus) */
#pragma clang assume_nonnull begin
#pragma mark C Utility Functions
dispatch_queue_t BGMGetDispatchQueue_PriorityUserInteractive(void);
#if defined(__cplusplus)
#pragma mark C++ Utility Functions
namespace BGM_Utils
{
// Used to explicitly cast from nullable to non-null. For Objective-C objects, use the BGMNN
// macro (above).
template <typename T>
inline T __nonnull NN(T __nullable v) {
BGMAssertNonNull(v);
return static_cast<T __nonnull>(v);
}
// Log (and swallow) errors returned by Mach functions. Returns false if there was an error.
bool LogIfMachError(const char* callerName,
const char* errorReturnedBy,
mach_error_t error);
// Similar to ThrowIfKernelError from CADebugMacros.h, but also logs (in debug builds) the
// Mach error string that corresponds to the error.
void ThrowIfMachError(const char* callerName,
const char* errorReturnedBy,
mach_error_t error);
// If function throws an exception, log an error and continue.
//
// Fails/stops debug builds. It's likely that if we log an error for an exception in release
// builds, even if it's expected (i.e. not a bug in Background Music), we'd want to know if
// it gets thrown during testing/debugging.
OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
int lineNumber,
const char* callerName,
const std::function<void(void)>& function);
OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
int lineNumber,
const char* callerName,
const char* __nullable message,
const std::function<void(void)>& function);
void LogException(const char* __nullable fileName,
int lineNumber,
const char* callerName,
const CAException& e);
void LogUnexpectedException(const char* __nullable fileName,
int lineNumber,
const char* callerName);
OSStatus LogUnexpectedExceptions(const char* callerName,
const std::function<void(void)>& function);
OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
int lineNumber,
const char* callerName,
const std::function<void(void)>& function);
// Log unexpected exceptions and continue.
//
// Generally, you don't want to use this unless the alternative is to crash. And even then
// crashing is often the better option. (Especially if we've added crash reporting by the
// time you're reading this.)
//
// Fails/stops debug builds.
//
// TODO: Allow a format string and args for the message.
OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
int lineNumber,
const char* callerName,
const char* __nullable message,
const std::function<void(void)>& function);
}
#endif /* defined(__cplusplus) */
#pragma clang assume_nonnull end
#endif /* SharedSource__BGM_Utils */