Initial commit

This commit is contained in:
2026-03-15 06:02:56 +09:00
commit 010c235e46
113 changed files with 28943 additions and 0 deletions

106
shared/VC_Types.h Normal file
View 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
View 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
View 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 */