mirror of
https://github.com/morgan9e/VolumeControl
synced 2026-04-14 16:24:07 +09:00
Initial commit
This commit is contained in:
106
shared/VC_Types.h
Normal file
106
shared/VC_Types.h
Normal file
@@ -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/>.
|
||||
|
||||
//
|
||||
// VC_Types.h
|
||||
// SharedSource
|
||||
//
|
||||
// Copyright © 2016, 2017, 2019, 2024 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef SharedSource__VC_Types
|
||||
#define SharedSource__VC_Types
|
||||
|
||||
// STL Includes
|
||||
#if defined(__cplusplus)
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
// System Includes
|
||||
#include <CoreAudio/AudioServerPlugIn.h>
|
||||
|
||||
|
||||
#pragma mark IDs
|
||||
|
||||
#define kVCDriverBundleID "com.volumecontrol.Driver"
|
||||
#define kVCAppBundleID "com.volumecontrol.App"
|
||||
|
||||
#define kVCDeviceUID "VCDevice"
|
||||
#define kVCDeviceModelUID "VCDeviceModelUID"
|
||||
|
||||
// The object IDs for the audio objects this driver implements.
|
||||
enum
|
||||
{
|
||||
kObjectID_PlugIn = kAudioObjectPlugInObject,
|
||||
kObjectID_Device = 2, // Belongs to kObjectID_PlugIn
|
||||
kObjectID_Stream_Input = 3, // Belongs to kObjectID_Device
|
||||
kObjectID_Stream_Output = 4, // Belongs to kObjectID_Device
|
||||
kObjectID_Volume_Output_Master = 5, // Belongs to kObjectID_Device
|
||||
kObjectID_Mute_Output_Master = 6, // Belongs to kObjectID_Device
|
||||
};
|
||||
|
||||
// AudioObjectPropertyElement docs: "Elements are numbered sequentially where 0 represents the
|
||||
// master element."
|
||||
static const AudioObjectPropertyElement kMasterChannel = kAudioObjectPropertyElementMaster;
|
||||
|
||||
#pragma mark VCDevice Custom Properties
|
||||
|
||||
enum
|
||||
{
|
||||
// A CFBoolean similar to kAudioDevicePropertyDeviceIsRunning except it ignores whether IO is running for
|
||||
// VCApp. This is so VCApp knows when it can stop doing IO to save CPU.
|
||||
kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanVCApp = 'runo',
|
||||
|
||||
// A UInt32 (0 or 1). The app sets this to hide/show the virtual device.
|
||||
kAudioDeviceCustomPropertyVCHidden = 'vchd',
|
||||
};
|
||||
|
||||
#pragma mark VCDevice Custom Property Addresses
|
||||
|
||||
// For convenience.
|
||||
|
||||
static const AudioObjectPropertyAddress kVCRunningSomewhereOtherThanVCAppAddress = {
|
||||
kAudioDeviceCustomPropertyDeviceIsRunningSomewhereOtherThanVCApp,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
#pragma mark Exceptions
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
class VC_InvalidClientException : public std::runtime_error {
|
||||
public:
|
||||
VC_InvalidClientException() : std::runtime_error("InvalidClient") { }
|
||||
};
|
||||
|
||||
class VC_InvalidClientPIDException : public std::runtime_error {
|
||||
public:
|
||||
VC_InvalidClientPIDException() : std::runtime_error("InvalidClientPID") { }
|
||||
};
|
||||
|
||||
class VC_DeviceNotSetException : public std::runtime_error {
|
||||
public:
|
||||
VC_DeviceNotSetException() : std::runtime_error("DeviceNotSet") { }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// Assume we've failed to start the output device if it isn't running IO after this timeout expires.
|
||||
//
|
||||
// Currently set to 30s because some devices, e.g. AirPlay, can legitimately take that long to start.
|
||||
static const UInt64 kStartIOTimeoutNsec = 30 * NSEC_PER_SEC;
|
||||
|
||||
#endif /* SharedSource__VC_Types */
|
||||
235
shared/VC_Utils.cpp
Normal file
235
shared/VC_Utils.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// VC_Utils.cpp
|
||||
// SharedSource
|
||||
//
|
||||
// Copyright © 2016, 2017 Kyle Neideck
|
||||
//
|
||||
|
||||
// Self Include
|
||||
#include "VC_Utils.h"
|
||||
|
||||
// Local Includes
|
||||
#include "VC_Types.h"
|
||||
|
||||
// System Includes
|
||||
#include <MacTypes.h>
|
||||
#include <mach/mach_error.h>
|
||||
#include <CoreFoundation/CoreFoundation.h> // For kCFCoreFoundationVersionNumber
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
dispatch_queue_t VCGetDispatchQueue_PriorityUserInteractive()
|
||||
{
|
||||
long queueClass;
|
||||
|
||||
// Compile-time check that QOS_CLASS_USER_INTERACTIVE can be used. It was added in 10.10.
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 // MAC_OS_X_VERSION_10_10
|
||||
// Runtime check for the same.
|
||||
if(floor(kCFCoreFoundationVersionNumber) > kCFCoreFoundationVersionNumber10_9)
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wpartial-availability"
|
||||
queueClass = QOS_CLASS_USER_INTERACTIVE;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Fallback for older versions.
|
||||
queueClass = DISPATCH_QUEUE_PRIORITY_HIGH;
|
||||
}
|
||||
|
||||
return dispatch_get_global_queue(queueClass, 0);
|
||||
}
|
||||
|
||||
namespace VC_Utils
|
||||
{
|
||||
// Forward declarations
|
||||
static OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const char* __nullable message,
|
||||
bool expected,
|
||||
const std::function<void(void)>& function);
|
||||
|
||||
#pragma mark Exception utils
|
||||
|
||||
bool LogIfMachError(const char* callerName,
|
||||
const char* errorReturnedBy,
|
||||
mach_error_t error)
|
||||
{
|
||||
if(error != KERN_SUCCESS)
|
||||
{
|
||||
char* errorStr = mach_error_string(error);
|
||||
LogError("%s: %s returned an error (%d): %s\n",
|
||||
callerName,
|
||||
errorReturnedBy,
|
||||
error,
|
||||
errorStr ? errorStr : "Unknown error");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThrowIfMachError(const char* callerName,
|
||||
const char* errorReturnedBy,
|
||||
mach_error_t error)
|
||||
{
|
||||
if(!LogIfMachError(callerName, errorReturnedBy, error))
|
||||
{
|
||||
Throw(CAException(error));
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
return LogAndSwallowExceptions(fileName, lineNumber, callerName, nullptr, true, function);
|
||||
}
|
||||
|
||||
OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const char* __nullable message,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
return LogAndSwallowExceptions(fileName, lineNumber, callerName, message, true, function);
|
||||
}
|
||||
|
||||
void LogException(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const CAException& e)
|
||||
{
|
||||
OSStatus err = e.GetError();
|
||||
const char err4CC[5] = CA4CCToCString(err);
|
||||
|
||||
LogError("%s:%d:%s: CAException, code: '%s' (%d).",
|
||||
(fileName ? fileName : ""),
|
||||
lineNumber,
|
||||
callerName,
|
||||
err4CC,
|
||||
err);
|
||||
}
|
||||
|
||||
void LogUnexpectedException(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName)
|
||||
{
|
||||
LogError("%s:%d:%s: Unknown unexpected exception.",
|
||||
(fileName ? fileName : ""),
|
||||
lineNumber,
|
||||
callerName);
|
||||
}
|
||||
|
||||
OSStatus LogUnexpectedExceptions(const char* callerName,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
return LogUnexpectedExceptions(nullptr, -1, callerName, nullptr, function);
|
||||
}
|
||||
|
||||
OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
return LogUnexpectedExceptions(fileName, lineNumber, callerName, nullptr, function);
|
||||
}
|
||||
|
||||
OSStatus LogUnexpectedExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const char* __nullable message,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
return LogAndSwallowExceptions(fileName, lineNumber, callerName, message, false, function);
|
||||
}
|
||||
|
||||
#pragma mark Implementation
|
||||
|
||||
static OSStatus LogAndSwallowExceptions(const char* __nullable fileName,
|
||||
int lineNumber,
|
||||
const char* callerName,
|
||||
const char* __nullable message,
|
||||
bool expected,
|
||||
const std::function<void(void)>& function)
|
||||
{
|
||||
try
|
||||
{
|
||||
function();
|
||||
}
|
||||
catch(const CAException& e)
|
||||
{
|
||||
// TODO: Can/should we log a stack trace somewhere? (If so, also in the following catch
|
||||
// block.)
|
||||
// TODO: Log a warning instead of an error for expected exceptions?
|
||||
OSStatus err = e.GetError();
|
||||
const char err4CC[5] = CA4CCToCString(err);
|
||||
|
||||
LogError("%s:%d:%s: %sCAException, code: '%s' (%d). %s%s %s %s ",
|
||||
(fileName ? fileName : ""),
|
||||
lineNumber,
|
||||
callerName,
|
||||
(expected ? "" : "Unexpected "),
|
||||
err4CC,
|
||||
err,
|
||||
(message ? message : ""),
|
||||
(message ? "." : ""),
|
||||
(expected ? "If you think this might be a bug:"
|
||||
: "Feel free to report this at"),
|
||||
"https://github.com/kyleneideck/BackgroundMusic/issues");
|
||||
|
||||
#if VC_StopDebuggerOnLoggedExceptions || VC_StopDebuggerOnLoggedUnexpectedExceptions
|
||||
#if !VC_StopDebuggerOnLoggedExceptions
|
||||
if(!expected)
|
||||
#endif
|
||||
{
|
||||
VCAssert(false, "CAException");
|
||||
}
|
||||
#endif
|
||||
return e.GetError();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
LogError("%s:%d:%s: %s exception. %s%s %s %s",
|
||||
(fileName ? fileName : ""),
|
||||
lineNumber,
|
||||
callerName,
|
||||
(expected ? "Unknown" : "Unexpected unknown"),
|
||||
(message ? message : ""),
|
||||
(message ? "." : ""),
|
||||
(expected ? "If you think this might be a bug:"
|
||||
: "Feel free to report this at"),
|
||||
"https://github.com/kyleneideck/BackgroundMusic/issues");
|
||||
|
||||
#if VC_StopDebuggerOnLoggedExceptions || VC_StopDebuggerOnLoggedUnexpectedExceptions
|
||||
VCAssert(false, "Unknown exception");
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
228
shared/VC_Utils.h
Normal file
228
shared/VC_Utils.h
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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/>.
|
||||
|
||||
//
|
||||
// VC_Utils.h
|
||||
// SharedSource
|
||||
//
|
||||
// Copyright © 2016-2020 Kyle Neideck
|
||||
//
|
||||
|
||||
#ifndef SharedSource__VC_Utils
|
||||
#define SharedSource__VC_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 and line numbers added.
|
||||
#if DEBUG
|
||||
#define VCAssert(inCondition, inMessage, ...) \
|
||||
if(!(inCondition)) \
|
||||
{ \
|
||||
DebugMsg("%s:%d:%s: " inMessage, \
|
||||
__FILE__, \
|
||||
__LINE__, \
|
||||
__FUNCTION__, \
|
||||
## __VA_ARGS__); \
|
||||
__ASSERT_STOP; \
|
||||
}
|
||||
#else
|
||||
#define VCAssert(inCondition, inMessage, ...)
|
||||
#endif /* DEBUG */
|
||||
|
||||
#define VCAssertNonNull(expression) \
|
||||
VCAssertNonNull2((expression), #expression)
|
||||
|
||||
#define VCAssertNonNull2(expression, expressionStr) \
|
||||
VCAssert((expression), \
|
||||
"%s:%d:%s: '%s' is null", \
|
||||
__FILE__, \
|
||||
__LINE__, \
|
||||
__FUNCTION__, \
|
||||
expressionStr);
|
||||
|
||||
// Used to give the first 3 arguments of VC_Utils::LogAndSwallowExceptions and
|
||||
// VC_Utils::LogUnexpectedExceptions (and probably others in future). Mainly so we can call those
|
||||
// functions directly instead of using the macro wrappers.
|
||||
#define VCDbgArgs __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 VCNonNullCastHelper<__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 VC_Utils::NN for other types.
|
||||
//
|
||||
// TODO: Replace existing non-null casts with this.
|
||||
#define VCNN(expression) ({ \
|
||||
__typeof((expression)) value = (expression); \
|
||||
VCAssertNonNull2(value, #expression); \
|
||||
VCNonNullCastHelper<__typeof((expression))>* helper; \
|
||||
(__typeof(helper.asNonNull) __nonnull)value; \
|
||||
})
|
||||
|
||||
#else /* __has_feature(objc_generics) */
|
||||
|
||||
#define VCNN(expression) ({ \
|
||||
id value = (expression); \
|
||||
VCAssertNonNull2(value, #expression); \
|
||||
value; \
|
||||
})
|
||||
|
||||
#endif /* __has_feature(objc_generics) */
|
||||
|
||||
#endif /* defined(__OBJC__) */
|
||||
|
||||
#pragma mark C++ Macros
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
#define VCLogException(exception) \
|
||||
VC_Utils::LogException(__FILE__, __LINE__, __FUNCTION__, exception)
|
||||
|
||||
#define VCLogExceptionIn(callerName, exception) \
|
||||
VC_Utils::LogException(__FILE__, __LINE__, callerName, exception)
|
||||
|
||||
#define VCLogAndSwallowExceptions(callerName, function) \
|
||||
VC_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, function)
|
||||
|
||||
#define VCLogAndSwallowExceptionsMsg(callerName, message, function) \
|
||||
VC_Utils::LogAndSwallowExceptions(__FILE__, __LINE__, callerName, message, function)
|
||||
|
||||
#define VCLogUnexpectedException() \
|
||||
VC_Utils::LogUnexpectedException(__FILE__, __LINE__, __FUNCTION__)
|
||||
|
||||
#define VCLogUnexpectedExceptionIn(callerName) \
|
||||
VC_Utils::LogUnexpectedException(__FILE__, __LINE__, callerName)
|
||||
|
||||
#define VCLogUnexpectedExceptions(callerName, function) \
|
||||
VC_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, function)
|
||||
|
||||
#define VCLogUnexpectedExceptionsMsg(callerName, message, function) \
|
||||
VC_Utils::LogUnexpectedExceptions(__FILE__, __LINE__, callerName, message, function)
|
||||
|
||||
#endif /* defined(__cplusplus) */
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
#pragma mark C Utility Functions
|
||||
|
||||
dispatch_queue_t VCGetDispatchQueue_PriorityUserInteractive(void);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
#pragma mark C++ Utility Functions
|
||||
|
||||
namespace VC_Utils
|
||||
{
|
||||
// Used to explicitly cast from nullable to non-null. For Objective-C objects, use the VCNN
|
||||
// macro (above).
|
||||
template <typename T>
|
||||
inline T __nonnull NN(T __nullable v) {
|
||||
VCAssertNonNull(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__VC_Utils */
|
||||
|
||||
Reference in New Issue
Block a user