mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-16 16:10:06 +02:00
... This makes it impossible to forget to include the EXPERIMENTAL definitions (such as when cutting and pasting code) and so get unintended quiet changes of behavior. The EXPERIMENTAL flags are now specified instead in new file Experimental.cmake
406 lines
9.2 KiB
C++
406 lines
9.2 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
DeviceChange.cpp
|
|
|
|
Leland Lucius
|
|
|
|
*******************************************************************//*!
|
|
|
|
\file DeviceChange.cpp
|
|
\brief
|
|
|
|
*//*******************************************************************/
|
|
|
|
#include "DeviceChange.h"
|
|
|
|
|
|
|
|
#if defined(EXPERIMENTAL_DEVICE_CHANGE_HANDLER)
|
|
|
|
#if defined(HAVE_DEVICE_CHANGE)
|
|
|
|
#include <wx/module.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/thread.h>
|
|
|
|
DECLARE_LOCAL_EVENT_TYPE(EVT_DEVICE_CHANGE, -1);
|
|
DEFINE_EVENT_TYPE(EVT_DEVICE_CHANGE);
|
|
|
|
#if defined(__WXMSW__)
|
|
|
|
#include <Windows.h>
|
|
#include <mmsystem.h>
|
|
#include <mmdeviceapi.h>
|
|
#include <audioclient.h>
|
|
|
|
class DeviceChangeListener final : public IMMNotificationClient,
|
|
public DeviceChangeInterface
|
|
{
|
|
public:
|
|
DeviceChangeListener()
|
|
{
|
|
mRefCnt = 1;
|
|
mEnumerator = NULL;
|
|
mEnabled = false;
|
|
mHandler = NULL;
|
|
}
|
|
|
|
virtual ~DeviceChangeListener()
|
|
{
|
|
if (mEnumerator)
|
|
{
|
|
mEnumerator->UnregisterEndpointNotificationCallback(this);
|
|
mEnumerator = NULL;
|
|
}
|
|
|
|
if (mHandler)
|
|
{
|
|
CoInitialize(NULL);
|
|
}
|
|
}
|
|
|
|
// IUnknown implementation
|
|
|
|
ULONG STDMETHODCALLTYPE AddRef()
|
|
{
|
|
return InterlockedIncrement(&mRefCnt);
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE Release()
|
|
{
|
|
ULONG cnt = InterlockedDecrement(&mRefCnt);
|
|
if (cnt == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface)
|
|
{
|
|
if (riid == IID_IUnknown)
|
|
{
|
|
AddRef();
|
|
*ppvInterface = (IUnknown *) this;
|
|
}
|
|
else if (riid == __uuidof(IMMNotificationClient))
|
|
{
|
|
AddRef();
|
|
*ppvInterface = (IMMNotificationClient *) this;
|
|
}
|
|
else
|
|
{
|
|
*ppvInterface = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// IMMDeviceChangeListener implementation
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged( EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
|
|
{
|
|
if (mEnabled)
|
|
{
|
|
mEnabled = false;
|
|
wxMutexGuiEnter();
|
|
wxCommandEvent e(EVT_DEVICE_CHANGE);
|
|
mHandler->AddPendingEvent(e);
|
|
wxMutexGuiLeave();
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
bool SetHandler(wxEvtHandler *handler)
|
|
{
|
|
mHandler = handler;
|
|
|
|
CoInitialize(NULL);
|
|
|
|
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
__uuidof(IMMDeviceEnumerator),
|
|
(void**)&mEnumerator);
|
|
if (hr == S_OK && mEnumerator)
|
|
{
|
|
mEnumerator->RegisterEndpointNotificationCallback(this);
|
|
}
|
|
|
|
return hr == S_OK && mEnumerator;
|
|
}
|
|
|
|
void Enable(bool enable)
|
|
{
|
|
mEnabled = enable;
|
|
}
|
|
|
|
private:
|
|
wxEvtHandler *mHandler;
|
|
bool mEnabled;
|
|
ULONG mRefCnt;
|
|
IMMDeviceEnumerator *mEnumerator;
|
|
};
|
|
|
|
#elif defined(__WXGTK__)
|
|
|
|
#include <libudev.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <locale.h>
|
|
#include <unistd.h>
|
|
|
|
class DeviceChangeListener final : public DeviceChangeInterface
|
|
{
|
|
public:
|
|
DeviceChangeListener()
|
|
{
|
|
mEnabled = false;
|
|
mHandler = NULL;
|
|
mThread = 0;
|
|
}
|
|
|
|
virtual ~DeviceChangeListener()
|
|
{
|
|
if (mThread)
|
|
{
|
|
pthread_cancel(mThread);
|
|
mThread = 0;
|
|
}
|
|
}
|
|
|
|
// IUnknown implementation
|
|
bool SetHandler(wxEvtHandler *handler)
|
|
{
|
|
mHandler = handler;
|
|
|
|
return pthread_create(&mThread, NULL, DeviceChangeListener::Listener, this) == 0;
|
|
}
|
|
|
|
void Enable(bool enable)
|
|
{
|
|
mEnabled = enable;
|
|
}
|
|
|
|
static void *Listener(void *parm)
|
|
{
|
|
DeviceChangeListener *This = (DeviceChangeListener *) parm;
|
|
|
|
// Instantiate the udev object
|
|
struct udev *udev = udev_new();
|
|
if (!udev)
|
|
{
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
// Instantiate the monitor object
|
|
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
|
|
|
|
// Start receiving notifications
|
|
udev_monitor_enable_receiving(mon);
|
|
|
|
// Get the file descriptor we'll wait on
|
|
int fd = udev_monitor_get_fd(mon);
|
|
|
|
while (true)
|
|
{
|
|
fd_set set;
|
|
|
|
FD_ZERO(&set);
|
|
FD_SET(fd, &set);
|
|
|
|
if (select(fd + 1, &set, NULL, NULL, NULL) < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (FD_ISSET(fd, &set))
|
|
{
|
|
struct udev_device *dev = udev_monitor_receive_device(mon);
|
|
if (dev)
|
|
{
|
|
#if 0
|
|
wxPrintf("Got Device\n");
|
|
wxPrintf(" Node: %s\n", udev_device_get_devnode(dev));
|
|
wxPrintf(" Subsystem: %s\n", udev_device_get_subsystem(dev));
|
|
wxPrintf(" Devtype: %s\n", udev_device_get_devtype(dev));
|
|
wxPrintf(" Action: %s\n", udev_device_get_action(dev));
|
|
#endif
|
|
if (This->mEnabled)
|
|
{
|
|
This->mEnabled = false;
|
|
wxMutexGuiEnter();
|
|
wxCommandEvent e(EVT_DEVICE_CHANGE);
|
|
This->mHandler->AddPendingEvent(e);
|
|
wxMutexGuiLeave();
|
|
}
|
|
|
|
udev_device_unref(dev);
|
|
}
|
|
}
|
|
}
|
|
|
|
udev_unref(udev);
|
|
|
|
pthread_exit(NULL);
|
|
}
|
|
|
|
private:
|
|
wxEvtHandler *mHandler;
|
|
bool mEnabled;
|
|
pthread_t mThread;
|
|
};
|
|
|
|
#elif defined(__WXMAC__)
|
|
|
|
#include <CoreAudio/CoreAudio.h>
|
|
|
|
class DeviceChangeListener final : public DeviceChangeInterface
|
|
{
|
|
public:
|
|
DeviceChangeListener()
|
|
{
|
|
mEnabled = false;
|
|
mHandler = NULL;
|
|
mListening = false;
|
|
}
|
|
|
|
virtual ~DeviceChangeListener()
|
|
{
|
|
if (mListening)
|
|
{
|
|
AudioObjectPropertyAddress property_address;
|
|
|
|
property_address.mSelector = kAudioHardwarePropertyDevices;
|
|
property_address.mScope = kAudioObjectPropertyScopeGlobal;
|
|
property_address.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
|
|
&property_address,
|
|
DeviceChangeListener::Listener,
|
|
this);
|
|
mListening = false;
|
|
}
|
|
}
|
|
|
|
// IUnknown implementation
|
|
bool SetHandler(wxEvtHandler *handler)
|
|
{
|
|
mHandler = handler;
|
|
|
|
AudioObjectPropertyAddress property_address;
|
|
|
|
property_address.mSelector = kAudioHardwarePropertyDevices;
|
|
property_address.mScope = kAudioObjectPropertyScopeGlobal;
|
|
property_address.mElement = kAudioObjectPropertyElementMaster;
|
|
|
|
mListening = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
|
|
&property_address,
|
|
DeviceChangeListener::Listener,
|
|
this) == 0;
|
|
}
|
|
|
|
void Enable(bool enable)
|
|
{
|
|
mEnabled = enable;
|
|
}
|
|
|
|
static OSStatus Listener(AudioObjectID objectID,
|
|
UInt32 numberAddresses,
|
|
const AudioObjectPropertyAddress inAddresses[],
|
|
void *clientData)
|
|
{
|
|
DeviceChangeListener *This = (DeviceChangeListener *) clientData;
|
|
|
|
for (int i = 0; i < numberAddresses; i++)
|
|
{
|
|
#if 0
|
|
wxPrintf("address %d\n", i);
|
|
wxPrintf("selector %08x\n", inAddresses[i].mSelector);
|
|
wxPrintf("scope %08x\n", inAddresses[i].mScope);
|
|
wxPrintf("element %08x\n", inAddresses[i].mElement);
|
|
#endif
|
|
if (This->mEnabled)
|
|
{
|
|
This->mEnabled = false;
|
|
wxMutexGuiEnter();
|
|
wxCommandEvent e(EVT_DEVICE_CHANGE);
|
|
This->mHandler->AddPendingEvent(e);
|
|
wxMutexGuiLeave();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
wxEvtHandler *mHandler;
|
|
bool mEnabled;
|
|
bool mListening;
|
|
};
|
|
#endif
|
|
|
|
BEGIN_EVENT_TABLE(DeviceChangeHandler, wxEvtHandler)
|
|
EVT_COMMAND(wxID_ANY, EVT_DEVICE_CHANGE, DeviceChangeHandler::OnChange)
|
|
EVT_TIMER(wxID_ANY, DeviceChangeHandler::OnTimer)
|
|
END_EVENT_TABLE()
|
|
|
|
DeviceChangeHandler::DeviceChangeHandler()
|
|
: wxEvtHandler()
|
|
{
|
|
mTimer.SetOwner(this);
|
|
mListener = std::make_unique<DeviceChangeListener>();
|
|
mListener->SetHandler(this);
|
|
mListener->Enable(true);
|
|
}
|
|
|
|
DeviceChangeHandler::~DeviceChangeHandler()
|
|
{
|
|
if (mListener)
|
|
mListener->Enable(false);
|
|
}
|
|
|
|
void DeviceChangeHandler::Enable(bool enable)
|
|
{
|
|
mListener->Enable(enable);
|
|
}
|
|
|
|
void DeviceChangeHandler::OnChange(wxCommandEvent & evt)
|
|
{
|
|
mTimer.Start(500, true);
|
|
}
|
|
|
|
void DeviceChangeHandler::OnTimer(wxTimerEvent & evt)
|
|
{
|
|
DeviceChangeNotification();
|
|
mListener->Enable(true);
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|