Added combo-boxes to allow audio device selection.
This commit is contained in:
@@ -24,6 +24,9 @@ AudioManagerWindows::~AudioManagerWindows() {
|
||||
if (micAudioDevice) {
|
||||
micAudioDevice->Release();
|
||||
}
|
||||
if (playbackAudioDevice) {
|
||||
playbackAudioDevice->Release();
|
||||
}
|
||||
audioDeviceEnumerator->Release();
|
||||
}
|
||||
|
||||
@@ -32,6 +35,10 @@ void AudioManagerWindows::init(AudioTabController* controller) {
|
||||
if (!audioDeviceEnumerator) {
|
||||
throw std::exception("Could not create audio device enumerator");
|
||||
}
|
||||
playbackAudioDevice = getDefaultPlaybackDevice(audioDeviceEnumerator);
|
||||
if (!playbackAudioDevice) {
|
||||
LOG(WARNING) << "Could not find a default recording device.";
|
||||
}
|
||||
micAudioDevice = getDefaultRecordingDevice(audioDeviceEnumerator);
|
||||
if (micAudioDevice) {
|
||||
micAudioEndpointVolume = getAudioEndpointVolume(micAudioDevice);
|
||||
@@ -40,20 +47,62 @@ void AudioManagerWindows::init(AudioTabController* controller) {
|
||||
}
|
||||
this->controller = controller;
|
||||
audioDeviceEnumerator->RegisterEndpointNotificationCallback((IMMNotificationClient*)this);
|
||||
policyConfig = getPolicyConfig();
|
||||
if (!policyConfig) {
|
||||
LOG(ERROR) << "Could not find PolicyConfig interface";
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManagerWindows::setPlaybackDevice(const std::string & id, bool notify) {
|
||||
if (!id.empty()) {
|
||||
auto dev = getDevice(audioDeviceEnumerator, id);
|
||||
if (!dev) {
|
||||
LOG(WARNING) << "Could not find playback device \"" << id << "\".";
|
||||
} else {
|
||||
playbackAudioDevice = dev;
|
||||
if (policyConfig) {
|
||||
LPWSTR devId;
|
||||
if (dev->GetId(&devId) >= 0) {
|
||||
policyConfig->SetDefaultEndpoint(devId, eConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notify) {
|
||||
controller->onNewPlaybackDevice();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getPlaybackDevName() {
|
||||
if (playbackAudioDevice) {
|
||||
return getDeviceName(playbackAudioDevice);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getPlaybackDevId() {
|
||||
if (playbackAudioDevice) {
|
||||
return getDeviceId(playbackAudioDevice);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
void AudioManagerWindows::setMirrorDevice(const std::string& id) {
|
||||
void AudioManagerWindows::setMirrorDevice(const std::string& id, bool notify) {
|
||||
if (id.empty()) {
|
||||
deleteMirrorDevice();
|
||||
} else {
|
||||
mirrorAudioDevice = getDevice(audioDeviceEnumerator, id);
|
||||
if (mirrorAudioDevice) {
|
||||
auto dev = getDevice(audioDeviceEnumerator, id);
|
||||
if (dev) {
|
||||
mirrorAudioDevice = dev;
|
||||
mirrorAudioEndpointVolume = getAudioEndpointVolume(mirrorAudioDevice);
|
||||
} else {
|
||||
LOG(WARNING) << "Could not find mirror device \"" << id << "\".";
|
||||
}
|
||||
}
|
||||
if (notify) {
|
||||
controller->onNewMirrorDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioManagerWindows::deleteMirrorDevice() {
|
||||
@@ -78,6 +127,13 @@ std::string AudioManagerWindows::getMirrorDevName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getMirrorDevId() {
|
||||
if (mirrorAudioDevice) {
|
||||
return getDeviceId(mirrorAudioDevice);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
float AudioManagerWindows::getMirrorVolume() {
|
||||
float value;
|
||||
if (mirrorAudioEndpointVolume && mirrorAudioEndpointVolume->GetMasterVolumeLevelScalar(&value) >= 0) {
|
||||
@@ -119,6 +175,26 @@ bool AudioManagerWindows::isMicValid() {
|
||||
return micAudioEndpointVolume != nullptr;
|
||||
}
|
||||
|
||||
void AudioManagerWindows::setMicDevice(const std::string & id, bool notify) {
|
||||
if (!id.empty()) {
|
||||
auto dev = getDevice(audioDeviceEnumerator, id);
|
||||
if (!dev) {
|
||||
LOG(WARNING) << "Could not find recording device \"" << id << "\".";
|
||||
} else {
|
||||
micAudioDevice = dev;
|
||||
if (policyConfig) {
|
||||
LPWSTR devId;
|
||||
if (dev->GetId(&devId) >= 0) {
|
||||
policyConfig->SetDefaultEndpoint(devId, eConsole);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notify) {
|
||||
controller->onNewRecordingDevice();
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getMicDevName() {
|
||||
if (micAudioDevice) {
|
||||
return getDeviceName(micAudioDevice);
|
||||
@@ -126,6 +202,13 @@ std::string AudioManagerWindows::getMicDevName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getMicDevId() {
|
||||
if (micAudioDevice) {
|
||||
return getDeviceId(micAudioDevice);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
float AudioManagerWindows::getMicVolume() {
|
||||
float value;
|
||||
if (micAudioEndpointVolume && micAudioEndpointVolume->GetMasterVolumeLevelScalar(&value) >= 0) {
|
||||
@@ -162,6 +245,14 @@ bool AudioManagerWindows::setMicMuted(bool value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> AudioManagerWindows::getRecordingDevices() {
|
||||
return getDevices(audioDeviceEnumerator, eCapture);
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> AudioManagerWindows::getPlaybackDevices() {
|
||||
return getDevices(audioDeviceEnumerator, eRender);
|
||||
}
|
||||
|
||||
|
||||
IMMDeviceEnumerator* AudioManagerWindows::getAudioDeviceEnumerator() {
|
||||
IMMDeviceEnumerator* pEnumerator;
|
||||
@@ -171,6 +262,24 @@ IMMDeviceEnumerator* AudioManagerWindows::getAudioDeviceEnumerator() {
|
||||
return pEnumerator;
|
||||
}
|
||||
|
||||
IPolicyConfig * AudioManagerWindows::getPolicyConfig() {
|
||||
IPolicyConfig* policyConfig = nullptr;
|
||||
// for Win 10
|
||||
auto hr = CoCreateInstance(__uuidof(CPolicyConfigClient), NULL, CLSCTX_INPROC, IID_IPolicyConfig2, (LPVOID *)&policyConfig);
|
||||
if (hr != S_OK) {
|
||||
hr = CoCreateInstance(__uuidof(CPolicyConfigClient), NULL, CLSCTX_INPROC, IID_IPolicyConfig1, (LPVOID *)&policyConfig);
|
||||
}
|
||||
// for Win Vista, 7, 8, 8.1
|
||||
if (hr != S_OK) {
|
||||
hr = CoCreateInstance(__uuidof(CPolicyConfigClient), NULL, CLSCTX_INPROC, IID_IPolicyConfig0, (LPVOID *)&policyConfig);
|
||||
}
|
||||
if (hr != S_OK) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return policyConfig;
|
||||
}
|
||||
}
|
||||
|
||||
IMMDevice* AudioManagerWindows::getDefaultRecordingDevice(IMMDeviceEnumerator* deviceEnumerator) {
|
||||
IMMDevice* pDevice;
|
||||
if (deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &pDevice) < 0) {
|
||||
@@ -179,6 +288,14 @@ IMMDevice* AudioManagerWindows::getDefaultRecordingDevice(IMMDeviceEnumerator* d
|
||||
return pDevice;
|
||||
}
|
||||
|
||||
IMMDevice * AudioManagerWindows::getDefaultPlaybackDevice(IMMDeviceEnumerator * deviceEnumerator) {
|
||||
IMMDevice* pDevice;
|
||||
if (deviceEnumerator->GetDefaultAudioEndpoint(eRender, eCommunications, &pDevice) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return pDevice;
|
||||
}
|
||||
|
||||
IMMDevice* AudioManagerWindows::getDevice(IMMDeviceEnumerator* deviceEnumerator, const std::string& id) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
std::wstring wid = converter.from_bytes(id);
|
||||
@@ -212,6 +329,33 @@ std::string AudioManagerWindows::getDeviceName(IMMDevice* device) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string AudioManagerWindows::getDeviceId(IMMDevice* device) {
|
||||
LPWSTR ppstrId;
|
||||
if (device->GetId(&ppstrId) >= 0) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
return converter.to_bytes(ppstrId);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> AudioManagerWindows::getDevices(IMMDeviceEnumerator * deviceEnumerator, EDataFlow dataFlow) {
|
||||
std::vector<std::pair<std::string, std::string>> retval;
|
||||
IMMDeviceCollection *ppDevices = nullptr;
|
||||
if (deviceEnumerator->EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE, &ppDevices) >= 0) {
|
||||
UINT count;
|
||||
ppDevices->GetCount(&count);
|
||||
for (UINT i = 0; i < count; ++i) {
|
||||
IMMDevice *device = nullptr;
|
||||
ppDevices->Item(i, &device);
|
||||
if (device) {
|
||||
retval.emplace_back(getDeviceId(device), getDeviceName(device));
|
||||
}
|
||||
}
|
||||
ppDevices->Release();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
HRESULT AudioManagerWindows::QueryInterface(REFIID riid, void ** ppvObject) {
|
||||
if (IID_IUnknown == riid) {
|
||||
@@ -240,10 +384,12 @@ HRESULT AudioManagerWindows::OnDeviceStateChanged(LPCWSTR, DWORD) {
|
||||
}
|
||||
|
||||
HRESULT AudioManagerWindows::OnDeviceAdded(LPCWSTR) {
|
||||
controller->onDeviceAdded();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT AudioManagerWindows::OnDeviceRemoved(LPCWSTR) {
|
||||
controller->onDeviceRemoved();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -266,6 +412,22 @@ HRESULT AudioManagerWindows::OnDefaultDeviceChanged(EDataFlow flow, ERole role,
|
||||
controller->onNewRecordingDevice();
|
||||
}
|
||||
}
|
||||
} else if (flow == eRender && role == eMultimedia) {
|
||||
if (audioDeviceEnumerator) {
|
||||
auto device = getDevice(audioDeviceEnumerator, pwstrDefaultDeviceId);
|
||||
if (device != playbackAudioDevice) {
|
||||
if (playbackAudioDevice) {
|
||||
playbackAudioDevice->Release();
|
||||
}
|
||||
playbackAudioDevice = device;
|
||||
if (!playbackAudioDevice) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
std::string name = converter.to_bytes(pwstrDefaultDeviceId);
|
||||
LOG(WARNING) << "Could not find playback device \"" << name << "\".";
|
||||
}
|
||||
controller->onNewPlaybackDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*char* pszFlow = (char*)"?????";
|
||||
char* pszRole = (char*)"?????";
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Mmdeviceapi.h>
|
||||
#include <Functiondiscoverykeys_devpkey.h>
|
||||
#include <Endpointvolume.h>
|
||||
#include "IPolicyConfig.h"
|
||||
|
||||
|
||||
// application namespace
|
||||
@@ -15,32 +16,45 @@ friend class AudioNotificationClient;
|
||||
private:
|
||||
AudioTabController* controller = nullptr;
|
||||
IMMDeviceEnumerator* audioDeviceEnumerator = nullptr;
|
||||
IMMDevice* playbackAudioDevice = nullptr;
|
||||
IMMDevice* mirrorAudioDevice = nullptr;
|
||||
IAudioEndpointVolume* mirrorAudioEndpointVolume = nullptr;
|
||||
IMMDevice* micAudioDevice = nullptr;
|
||||
IAudioEndpointVolume* micAudioEndpointVolume = nullptr;
|
||||
IPolicyConfig* policyConfig = nullptr;
|
||||
|
||||
public:
|
||||
~AudioManagerWindows();
|
||||
|
||||
virtual void init(AudioTabController* controller) override;
|
||||
|
||||
virtual void setMirrorDevice(const std::string& id) override;
|
||||
virtual void deleteMirrorDevice() override;
|
||||
virtual void setPlaybackDevice(const std::string& id, bool notify = true) override;
|
||||
virtual std::string getPlaybackDevName() override;
|
||||
virtual std::string getPlaybackDevId() override;
|
||||
|
||||
virtual void setMirrorDevice(const std::string& id, bool notify = true) override;
|
||||
virtual bool isMirrorValid() override;
|
||||
virtual std::string getMirrorDevName() override;
|
||||
virtual std::string getMirrorDevId() override;
|
||||
virtual float getMirrorVolume() override;
|
||||
virtual bool setMirrorVolume(float value) override;
|
||||
virtual bool getMirrorMuted() override;
|
||||
virtual bool setMirrorMuted(bool value) override;
|
||||
|
||||
virtual bool isMicValid() override;
|
||||
virtual void setMicDevice(const std::string& id, bool notify = true) override;
|
||||
virtual std::string getMicDevName() override;
|
||||
virtual std::string getMicDevId() override;
|
||||
virtual float getMicVolume() override;
|
||||
virtual bool setMicVolume(float value) override;
|
||||
virtual bool getMicMuted() override;
|
||||
virtual bool setMicMuted(bool value) override;
|
||||
|
||||
virtual std::vector<std::pair<std::string, std::string>> getRecordingDevices() override;
|
||||
virtual std::vector<std::pair<std::string, std::string>> getPlaybackDevices() override;
|
||||
|
||||
void deleteMirrorDevice();
|
||||
|
||||
// from IMMNotificationClient
|
||||
virtual HRESULT QueryInterface(REFIID riid, void ** ppvObject) override;
|
||||
virtual ULONG AddRef(void) override;
|
||||
@@ -53,11 +67,15 @@ public:
|
||||
|
||||
private:
|
||||
IMMDeviceEnumerator* getAudioDeviceEnumerator();
|
||||
IPolicyConfig* getPolicyConfig();
|
||||
IMMDevice* getDefaultRecordingDevice(IMMDeviceEnumerator* deviceEnumerator);
|
||||
IMMDevice* getDefaultPlaybackDevice(IMMDeviceEnumerator* deviceEnumerator);
|
||||
IMMDevice* getDevice(IMMDeviceEnumerator* deviceEnumerator, const std::string& id);
|
||||
IMMDevice* getDevice(IMMDeviceEnumerator* deviceEnumerator, LPCWSTR id);
|
||||
IAudioEndpointVolume* getAudioEndpointVolume(IMMDevice* device);
|
||||
std::string getDeviceName(IMMDevice* device);
|
||||
std::string getDeviceId(IMMDevice* device);
|
||||
std::vector<std::pair<std::string, std::string>> getDevices(IMMDeviceEnumerator* deviceEnumerator, EDataFlow dataFlow);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// PolicyConfig.h
|
||||
// Undocumented COM-interface IPolicyConfig.
|
||||
// Use for set default audio render endpoint
|
||||
// @author EreTIk
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
// ca286fc3-91fd-42c3-8e9b-caafa66242e3
|
||||
static const GUID IID_IPolicyConfig2 = { 0xCA286FC3, 0x91FD, 0x42C3, { 0x8E, 0x9B, 0xCA, 0xAF, 0xA6, 0x62, 0x42, 0xE3 } };
|
||||
|
||||
// 6be54be8-a068-4875-a49d-0c2966473b11
|
||||
static const GUID IID_IPolicyConfig1 = { 0x6BE54BE8, 0xA068, 0x4875, { 0xA4, 0x9D, 0x0C, 0x29, 0x66, 0x47, 0x3B, 0x11 } };
|
||||
|
||||
// f8679f50-850a-41cf-9c72-430f290290c8
|
||||
static const GUID IID_IPolicyConfig0 = { 0xF8679F50, 0x850A, 0x41CF, { 0x9C, 0x72, 0x43, 0x0F, 0x29, 0x02, 0x90, 0xC8 } };
|
||||
|
||||
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
|
||||
|
||||
interface IPolicyConfig : public IUnknown
|
||||
{
|
||||
public:
|
||||
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
|
||||
PCWSTR
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
PINT64,
|
||||
PINT64
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
int,
|
||||
const PROPERTYKEY &,
|
||||
const PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
int,
|
||||
const PROPERTYKEY &,
|
||||
const PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
__in PCWSTR wszDeviceId,
|
||||
__in ERole eRole
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT
|
||||
);
|
||||
};
|
||||
|
||||
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620")
|
||||
IPolicyConfigVista;
|
||||
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862")
|
||||
CPolicyConfigVistaClient;
|
||||
// ----------------------------------------------------------------------------
|
||||
// class CPolicyConfigVistaClient
|
||||
// {294935CE-F637-4E7C-A41B-AB255460B862}
|
||||
//
|
||||
// interface IPolicyConfigVista
|
||||
// {568b9108-44bf-40b4-9006-86afe5b5a620}
|
||||
//
|
||||
// Query interface:
|
||||
// CComPtr<IPolicyConfigVista> PolicyConfig;
|
||||
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
|
||||
//
|
||||
// @compatible: Windows Vista and Later
|
||||
// ----------------------------------------------------------------------------
|
||||
interface IPolicyConfigVista : public IUnknown
|
||||
{
|
||||
public:
|
||||
|
||||
virtual HRESULT GetMixFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX **
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
|
||||
PCWSTR,
|
||||
INT,
|
||||
WAVEFORMATEX **
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
|
||||
PCWSTR,
|
||||
WAVEFORMATEX *,
|
||||
WAVEFORMATEX *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
|
||||
PCWSTR,
|
||||
INT,
|
||||
PINT64,
|
||||
PINT64
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
|
||||
PCWSTR,
|
||||
PINT64
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
|
||||
PCWSTR,
|
||||
struct DeviceShareMode *
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
|
||||
PCWSTR,
|
||||
int,
|
||||
const PROPERTYKEY &,
|
||||
const PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
|
||||
PCWSTR,
|
||||
int,
|
||||
const PROPERTYKEY &,
|
||||
const PROPVARIANT *
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
|
||||
__in PCWSTR wszDeviceId,
|
||||
__in ERole eRole
|
||||
);
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
|
||||
PCWSTR,
|
||||
INT
|
||||
); // not available on Windows 7, use method from IPolicyConfig
|
||||
};
|
||||
Reference in New Issue
Block a user