diff --git a/assets.qrc b/assets.qrc new file mode 100644 index 0000000..8b63932 --- /dev/null +++ b/assets.qrc @@ -0,0 +1,5 @@ + + + assets/notificationAreaIcon.png + + diff --git a/assets/SoundVolumeView.exe b/assets/SoundVolumeView.exe new file mode 100644 index 0000000..4c7a7d2 Binary files /dev/null and b/assets/SoundVolumeView.exe differ diff --git a/assets/notificationAreaIcon.png b/assets/notificationAreaIcon.png new file mode 100644 index 0000000..0252332 Binary files /dev/null and b/assets/notificationAreaIcon.png differ diff --git a/bueno.bat b/bueno.bat index cd190f0..2c8ac1c 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,2 +1,4 @@ qmake -o build\Makefile .\qtest.pro -mingw32-make.exe -C .\build -f Makefile.Release +copy /Y /B .\assets\SoundVolumeView.exe .\build\debug +copy /Y /B .\assets\SoundVolumeView.exe .\build\release +mingw32-make.exe -C .\build -f Makefile diff --git a/qtest.pro b/qtest.pro index 7f7dbb8..046d8d8 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,8 +1,16 @@ -CONFIG += debug console -QT += widgets +QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview +QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v +LIBS += -LC:/capybara/libclang/x86_64-w64-mingw32/lib -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 +#"kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 +DEFINES += DEBUG QT_LOGGING_TO_CONSOLE=1 WIN32_LEAN_AND_MEAN +CONFIG += debug + +QT += widgets network INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" -SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp -HEADERS += qtclasses.h backlasses.h contclasses.h global.h +SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp backsessionclasses.cpp contclasses.cpp contsessionclasses.cpp +HEADERS += qtclasses.h backlasses.h backsessionclasses.h contclasses.h contsessionclasses.h global.h debug.h backfuncs.h ipolicyconfig.h msinclude.h +RESOURCES = assets.qrc + #DESTDIR += "build" diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h new file mode 100644 index 0000000..b8e1c6c --- /dev/null +++ b/src/back/backfuncs.h @@ -0,0 +1,32 @@ +GUID inline NGuidToGUID(NGuid guid) { + GUID msGuid = GUID(); + msGuid.Data1 = guid.data1; + msGuid.Data2 = guid.data2; + msGuid.Data3 = guid.data3; + for (int i = 0; i < 8; i++){ + msGuid.Data4[i] = guid.data4[i]; + //log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); + //log_debugcpp(print_as_binary(8, uint32_t, msGuid.Data4[i])); + } + //log_debugcpp("MSGUID DATA1: " << msGuid.Data1); + //log_debugcpp("MSGUID DATA2: " << msGuid.Data2); + //log_debugcpp("MSGUID DATA3: " << msGuid.Data3); + + return msGuid; +} + +NGuid inline GUIDToNGuid(LPGUID msGuid){ + NGuid guid = NGuid(); + guid.data1 = msGuid->Data1; + guid.data2 = msGuid->Data2; + guid.data3 = msGuid->Data3; + for (int i = 0; i < 8; i++){ + guid.data4[i] = msGuid->Data4[i]; + //log_debugcpp("GUID DATA4 BYTE " << i << ": "); + //log_debugcpp(print_as_binary(8, uint32_t, guid.data4[i])); + } + //log_debugcpp("GUID DATA1: " << guid.data1); + //log_debugcpp("GUID DATA2: " << guid.data2); + //log_debugcpp("GUID DATA3: " << guid.data3); + return guid; +} diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 61e3562..8960c49 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,40 +1,563 @@ #include +#include -Endpoint::Endpoint(IMMDevice* ep){ +EndpointNewSessionCallback::EndpointNewSessionCallback(EndpointHandler* eph){ + this->eph = eph; +} + +ULONG EndpointNewSessionCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointNewSessionCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointNewSessionCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioSessionNotification) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointNewSessionCallback::OnSessionCreated(IAudioSessionControl *NewSession) { + if (eph->getFlow() == Flows::FLOW_CAPTURE) return S_OK; + + HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + IAudioSessionControl2* sessionControl; + //ISimmpleAudioVolume* sessionVolume; + if (FAILED(NewSession->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl))) { log_wdebugcpp(L"no nueva sesion......"); }; + if (sessionControl) { + sessionControl->AddRef(); + //sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); + Session* newSession = new Session(this->eph->getEndpoint(), sessionControl); + eph->addSessionSendFront(newSession); + } + + if (result == S_OK) + CoUninitialize(); + + return S_OK; +} + +EndpointVolumeCallback::EndpointVolumeCallback(Endpoint* ep){ + this->ep = ep; +} + +ULONG EndpointVolumeCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointVolumeCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointVolumeCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioEndpointVolumeCallback) == riid) + { + AddRef(); + *ppvInterface = (IAudioEndpointVolumeCallback*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { + if (pNotify == NULL) return E_INVALIDARG; + + //delete osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; + //osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.freeData4(); + //Could've made a function or = override to hide this within Nguid, but back in cont = bad. + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ + = pNotify->guidEventContext.Data1; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ + = pNotify->guidEventContext.Data2; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ + = pNotify->guidEventContext.Data3; + for(int i = 0; i < 8 /* Data4 size */; i++){ + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = pNotify->guidEventContext.Data4[i]; + } + + //memcpy(&osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); + Flows flow = this->ep->getFlow(); + EndpointHandler* eph = nullptr; + if (flow & Flows::FLOW_PLAYBACK) { + eph = osh->getPlaybackEndpointHandlers().at(this->ep->getIndex()); + } else { + eph = osh->getCaptureEndpointHandlers().at(this->ep->getIndex()); + } + + eph->getCallbackInfo()->muted = pNotify->bMuted; + eph->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; + eph->getCallbackInfo()->channels = pNotify->nChannels; + + + UINT j = 0; + //todo: do while here caused stack corruption; sus + while(j < pNotify->nChannels) { + if (flow & Flows::FLOW_PLAYBACK) + eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + else + eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + j++; + } + return S_OK; +} + +/* + * EndpointSituationCallback::EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices){ + * this->deviceEnumerator = deviceEnumerator; + * this->playbackDevices = playbackDevices; + * } + * + */ +//todo: not on construct since it expects them to already exist; smells like refactor! +void EndpointSituationCallback::fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices, std::vector captureDevices){ + this->deviceEnumerator = deviceEnumerator; + this->playbackDevices = playbackDevices; + this->captureDevices = captureDevices; +} + +ULONG EndpointSituationCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointSituationCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointSituationCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IMMNotificationClient) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole role,LPCWSTR pwstrDeviceId) { + if (flow == EDataFlow::eCapture) return E_INVALIDARG; + + Roles nRole; + switch (role) { + case ERole::eConsole: + nRole = Roles::ROLE_CONSOLE; + break; + case ERole::eMultimedia: + nRole = Roles::ROLE_MULTIMEDIA; + break; + case ERole::eCommunications: + nRole = Roles::ROLE_COMMUNICATIONS; + break; + } + std::wstring wstringEndpointId = pwstrDeviceId; + log_wdebugcpp(L"we got za defol 4 " + wstringEndpointId); + osh->changeFrontDefaultsCallback(nRole, wstringEndpointId); + + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDeviceAdded(LPCWSTR pwstrDeviceId) { + log_wdebugcpp(L"ayo we eventing za adin " + std::wstring(pwstrDeviceId)); + return S_OK; +}; + +HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { + log_wdebugcpp(L"ayo we eventing za rmovin " + std::wstring(pwstrDeviceId)); + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { + std::wstring endpointId = std::wstring(pwstrDeviceId); + switch (dwNewState){ + case DEVICE_STATE_ACTIVE: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_ACTIVE); + break; + case DEVICE_STATE_DISABLED: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_DISABLED); + break; + case DEVICE_STATE_NOTPRESENT: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_NOTPRESENT); + break; + case DEVICE_STATE_UNPLUGGED: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_UNPLUGGED); + break; + } + + return S_OK; +} + +HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { + + /* + * log_debugcpp(" -->Changed device property " + + * key.fmtid.Data1 + key.fmtid.Data2 + key.fmtid.Data3 + "\n" + + * key.fmtid.Data4[0]+ key.fmtid.Data4[1]+ "\n"+ + * key.fmtid.Data4[2]+ key.fmtid.Data4[3] + "\n"+ + * key.fmtid.Data4[4]+ key.fmtid.Data4[5] + "\n"+ + * key.fmtid.Data4[6]+ key.fmtid.Data4[7]+ "\n"+ + * " pid " + key.pid); + */ + + return S_OK; +} + +Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; - if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { log_debugcpp("si"); }; - //Obtaining friendly name: IPropertyStore creates PROPVARIANT per field - // hr = endpointPtr->GetId(&endpointID); + this->idx = idx; + + /* + * It can't multiflag, it's that stupid. MS momento. + * Only shows most relevant flag according to MS, i.e. 0110 sends 0010 + */ + //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas + if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-1);}; + + if(this->endpointState == EndpointState::ENDPOINT_ACTIVE) { + activateEndpointVolume(); + } + + reloadEndpointChannels(); + + /* todo: check header + * if(FAILED(endpoint->Activate(__uuidof(IAudioMeterInformation), + * CLSCTX_ALL, NULL, (void**)&endpointPeakMeter))) { log_debugcpp("peakbros..."); } + */ + + //todo:: atexit into exit Gather ID + LPWSTR tempString = nullptr; + if (FAILED(endpoint->GetId(&tempString))) {exit(-1);}; + endpointId = std::wstring(tempString); + log_wdebugcpp(endpointId); + CoTaskMemFree(tempString); + endpoint->OpenPropertyStore(STGM_READ, &properties); PROPVARIANT pv; properties->GetValue(PKEY_Device_FriendlyName , &pv); - friendlyName = pv.pwszVal; + if (pv.pwszVal == nullptr) + friendlyName = L"Unnamed Not Present Endpoint"; + else + friendlyName = std::wstring(pv.pwszVal); + + this->setFlow(); + if (this->flow == Flows::FLOW_PLAYBACK) { + activateEndpointSessions(); + } } -LPWSTR Endpoint::getName(){ +/* + * Endpoint::Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; + */ + +void Endpoint::activateEndpointSessions() { + //sessionManager; + if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**) &sessionManager))) { log_wdebugcpp(L"sesionbros..."); return; } + + IAudioSessionEnumerator* sessionEnumerator = nullptr; + if (FAILED(sessionManager->GetSessionEnumerator(&sessionEnumerator))) { log_wdebugcpp(L"sesEnumeratorBros..."); return; } + + int sessionCount; + sessionEnumerator->GetCount(&sessionCount); + for (int i = 0; i < sessionCount; i++) { + IAudioSessionControl* sessionControlTmp; + sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp); + //todo:: asegurar lo del dynamic_cast + IAudioSessionControl2* sessionControl; + sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl); + sessionControl->AddRef(); + sessionControlTmp->Release(); + Session* session = new Session(this, sessionControl, (size_t)i); + endpointSessions.push_back(session); + } + sessionEnumerator->Release(); +} + +void Endpoint::addSession(Session* session) { + session->setIndex(this->getSessionCount()); + endpointSessions.push_back(session); +} + +void Endpoint::activateEndpointVolume() { + //bool extraThread = false; + /* + * Forgive me, for MS has sinned, and now I must too. + */ + if (this->endpointVolume == nullptr){ + HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume); + //if (endpointVolume == nullptr) { //why they returning 0 after dealing with the error jfc CO_E_NOTINITIALIZED) { + //CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + //extraThread = true; + //goto initialized; + //} + //log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); + if (result == S_OK) + CoUninitialize(); + } +} + +void Endpoint::reloadEndpointChannels() { + if (this->endpointState == DEVICE_STATE_ACTIVE) { + if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {log_debugcpp("get channel count fail");};/* */ + } +} + +void Endpoint::setIndex(uint64_t idx){ + this->idx = idx; +} + +uint64_t Endpoint::getIndex(){ + return idx; +} + +std::wstring Endpoint::getName(){ return friendlyName; } -float Endpoint::getVolume(){ +std::wstring Endpoint::getId(){ + return endpointId; +} + +float Endpoint::getVolume(int channel){ float volume; - if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { /* log_debugcpp("si") */;} + } else { + if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { /* log_debugcpp("si"); */} + } return volume; } -void Endpoint::setVolume(float volume) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; +uint32_t Endpoint::getChannelCount(){ + return (uint32_t)channelCount; } -//Endpoint::~Endpoint(){ -// free(friendlyName); -// properties->Release(); -// endpointVolume->Release(); -// endpoint->Release(); -//} +bool Endpoint::getMute(){ + BOOL mut; + if(FAILED(endpointVolume->GetMute(&mut))) { /* TIP: Below */ } + bool mute = (bool)mut; + return mute; +} -void Overseer::initCOMLibrary(){ - if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { log_debugcpp("si"); }; +void Endpoint::setState(uint8_t state){ + this->endpointState = state; + if(state == EndpointState::ENDPOINT_ACTIVE) { + this->activateEndpointVolume(); + this->reloadEndpointChannels(); + } +} + +size_t Endpoint::getState(){ + return this->endpointState; +} + +void Endpoint::setVolume(NGuid guid, int channel, float volume) { + //TIP: There used to be log messages here. Now, it's a ghost town. + GUID tempMsGuid = NGuidToGUID(guid); + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) {}; + } else { + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) {}; + } +} + +void Endpoint::setMute(NGuid guid, bool muted) { + GUID tempMsGuid = NGuidToGUID(guid); + if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"EndpointVolume null?")); }; +} + +void Endpoint::setVolumeCallback(EndpointVolumeCallback *epc){ + if(endpointVolume == nullptr) { + this->activateEndpointVolume(); + } + endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); +} + +void Endpoint::removeVolumeCallback(EndpointVolumeCallback *epc){ + endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); +} + +Roles Endpoint::getRoles(){ + return this->endpointRoles; +} + +void Endpoint::setRoles(Roles role){ + //otro exe momento + STARTUPINFOEXW startupConfig; + PROCESS_INFORMATION processInfo; + SecureZeroMemory(&startupConfig, sizeof(STARTUPINFOEXW)); + SecureZeroMemory(&startupConfig.StartupInfo, sizeof(STARTUPINFOW)); + startupConfig.StartupInfo.cb = sizeof(STARTUPINFOEXW); + SecureZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); + + std::wstring command = L"SoundVolumeView.exe /SetDefault " + endpointId + L" "; + std::wstring troublePair = L"0"; + switch (role) { + case Roles::ROLE_ALL: + /* + * console or multimedia, one sends both, at least for now; + * either cos of ms or dis guy; + * no choice but to treat them as one for now. + * command += L"all"; and nothing else would've been nice... + */ + troublePair = command + troublePair; + if(CreateProcessW( + NULL, + (wchar_t*)troublePair.c_str(), + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + (LPSTARTUPINFOW)&startupConfig, + &processInfo + ) == true) { + WaitForSingleObject(processInfo.hProcess, INFINITE ); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + command += L"2"; + break; + case Roles::ROLE_CONSOLE: + command += std::to_wstring(0); + break; + case Roles::ROLE_MULTIMEDIA: + command += std::to_wstring(1); + break; + case Roles::ROLE_COMMUNICATIONS: + command += std::to_wstring(2); + break; + } + + if(CreateProcessW( + NULL, + (wchar_t*)command.c_str(), + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + (LPSTARTUPINFOW)&startupConfig, + &processInfo + ) == true) { + WaitForSingleObject(processInfo.hProcess, INFINITE ); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } +} + +void Endpoint::assignRoles(Roles role){ + Roles roles = (Roles)(endpointRoles | role); + this->endpointRoles = roles; +} + +void Endpoint::removeRoles(Roles role){ + Roles roles = (Roles)(endpointRoles ^ role); + this->endpointRoles = roles; +} + +void Endpoint::setFlow() { + IMMEndpoint* flowGetter; + //this should be as simple as writing IID_IMMEndpoint, but it just won't find the macro, so I copied it. Sad. + GUID manual; + manual.Data1 = 0x1be09788; + manual.Data2 = 0x6894; + manual.Data3 = 0x4089; + manual.Data4[0] = 0x85; + manual.Data4[1] = 0x86; + manual.Data4[2] = 0x9a; + manual.Data4[3] = 0x2a; + manual.Data4[4] = 0x6c; + manual.Data4[5] = 0x26; + manual.Data4[6] = 0x5a; + manual.Data4[7] = 0xc5; + if(FAILED(this->endpoint->QueryInterface((const _GUID)manual, (void**)&flowGetter))) + { log_debugcpp("no flow..."); } + EDataFlow MSflow; + HRESULT vafllar = flowGetter->GetDataFlow(&MSflow); + this->flow = (MSflow == EDataFlow::eRender ? Flows::FLOW_PLAYBACK : Flows::FLOW_CAPTURE); + flowGetter->Release(); +} + +Flows Endpoint::getFlow() { + return this->flow; +} + +/* sessions */ +std::vector Endpoint::getSessions() { + return endpointSessions; +} + +size_t Endpoint::getSessionCount() { + return endpointSessions.size(); +} + +void Endpoint::registerNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->RegisterSessionNotification(ensc); +} + +void Endpoint::unregisterNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->UnregisterSessionNotification(ensc); +} + +Endpoint::~Endpoint(){ + log_wdebugcpp(L"murio endpoint-san uwu"); + properties->Release(); + endpointVolume->Release(); + endpoint->Release(); + sessionManager->Release(); +} + +void Overseer::initCOMLibrary() { + OutputDebugStringW(L"EPWidget creation\n"); + if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { + log_debugcpp("si"); }; //Retrieving endpoint enumerator @@ -44,58 +567,148 @@ void Overseer::initCOMLibrary(){ (void**)&deviceEnumerator)) ) { log_debugcpp("si"); }; + GUID tempGuid; + if(FAILED(CoCreateGuid(&tempGuid))) { log_debugcpp("Failed to obtain GUID: " ); }; + //todo: wtf? why is it working? floats are ptrs... + this->guid = GUIDToNGuid(&tempGuid); + + //if(FAILED(CoCreateInstance(__uuidof(CPolicyConfigClient), + // NULL, CLSCTX_ALL, __uuidof(IPolicyConfig10), (LPVOID *)&policyConfig))) {exit(-1);} + + //TODO: Release lpguid? + //TODO: Uninitialize COM } -void Overseer::reloadEndpoints() { +void Overseer::reloadEndpoints(Flows flow) { IMMDeviceCollection *deviceCollection; + unsigned int numEndpoints; + EDataFlow MSflow = (flow == Flows::FLOW_PLAYBACK ? EDataFlow::eRender : EDataFlow::eCapture); // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) )) + // NOTPRESENT shows a lot of garbage, unnamed devices. + if(FAILED(deviceEnumerator->EnumAudioEndpoints(MSflow, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED, &deviceCollection) )) { log_debugcpp("si"); }; - - //Counting them - if(FAILED(deviceCollection->GetCount(&numPlaybackEndpoints))) { log_debugcpp("si");}; - if(numPlaybackEndpoints == 0) { log_debugcpp("si"); }; - + /* + * Counting them + */ + if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; + if(numEndpoints == 0) { log_debugcpp("si"); }; - //Retrieving actual endpoints and storing them on their own class - for (unsigned int i = 0; i < numPlaybackEndpoints; i++){ - IMMDevice *temp; + /* + * Retrieving actual endpoints and storing them on their own class + */ + IMMDevice *temp; + for (unsigned int i = 0; i < numEndpoints; i++){ if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; - Endpoint *endpoint = new Endpoint(temp); - this->playbackDevices.push_back(endpoint); - //TODO: le porblemx std::cout << "ola" << std::endl; + Endpoint *endpoint = new Endpoint(temp, i); + + if (flow == Flows::FLOW_PLAYBACK) + this->playbackDevices.push_back(endpoint); + else + this->captureDevices.push_back(endpoint); + //TODO: le porblemx std::cout + "ola" + std::endl; } deviceCollection->Release(); + + /* + * Discerning default endpoints per role + * order: console, multimedia, communications + */ + for(int i = 0; i < ERole_enum_count; i++){ + ERole val; + switch(i) { + case 0: + val = eConsole; + break; + case 1: + val = eMultimedia; + break; + case 2: + val = eCommunications; + break; + } + deviceEnumerator->GetDefaultAudioEndpoint(MSflow, val, &temp); + LPWSTR id = nullptr; + + if (flow == Flows::FLOW_PLAYBACK) { + for (unsigned int j = 0; j < numEndpoints; j++) { + std::wstring eptId = playbackDevices.at(j)->getId(); + temp->GetId(&id); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); + if (comparison - 2 == 0) { + log_wdebugcpp(L"ola defaul playback de " + + std::to_wstring(i) + L" es " + id); + playbackDevices.at(j)->assignRoles((Roles)(1 << i)); + } + } + } else { + for (unsigned int j = 0; j < numEndpoints; j++){ + std::wstring eptId = captureDevices.at(j)->getId(); + temp->GetId(&id); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); + if (comparison - 2 == 0) { + log_wdebugcpp(L"ola defaul capture de " + + std::to_wstring(i) + L" es " + id); + captureDevices.at(j)->assignRoles((Roles)(1 << i)); + } + } + } + } } -Overseer::Overseer(){ +Endpoint* Overseer::addEndpoint(std::wstring endpointId, /* out */Flows* flow = nullptr) { + IMMDevice* newep; + if(FAILED(deviceEnumerator->GetDevice((LPCWSTR)endpointId.c_str(), &newep))) + log_debugcpp("ay caramba con la hot metida."); + + Endpoint *endpoint = new Endpoint(newep); + + Flows getFlow = endpoint->getFlow(); + if (getFlow == Flows::FLOW_PLAYBACK) { + endpoint->setIndex(osh->getPlaybackEndpointsCount()); + this->playbackDevices.push_back(endpoint); + } else { + endpoint->setIndex(osh->getCaptureEndpointsCount()); + this->captureDevices.push_back(endpoint); + } + if (flow != nullptr) *flow = getFlow; + return endpoint; +} + +Overseer::Overseer() { //: epsc(deviceEnumerator, playbackDevices){ //Initializing COM library + log_debugcpp("Initializing Overseer"); initCOMLibrary(); //Obtaining playback endpoint collection on this point in time - reloadEndpoints(); + reloadEndpoints(Flows::FLOW_PLAYBACK); + //reloadEndpoints(Flows::FLOW_CAPTURE); + + //Registering for endpoint information callback + this->epsc.fill(deviceEnumerator, playbackDevices, captureDevices); + if(FAILED(deviceEnumerator->RegisterEndpointNotificationCallback(((IMMNotificationClient*)&epsc)))) { log_debugcpp("when no enchufas......"); } } -//Overseer::int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint){ -//if (FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &endpointPtr))) -// return 1; -//return 0; -//} - -//int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); - +NGuid Overseer::getGuid() { + return guid; +} + std::vector Overseer::getPlaybackEndpoints() { return playbackDevices; } -//Overseer::~Overseer(){ - // deviceEnumerator->Release(); - // for(unsigned long long i = 0; i < playbackDevices.size(); i++){ - //delete(playbackDevices.at(i)); - //} - //} +std::vector Overseer::getCaptureEndpoints() { + return captureDevices; +} + +Overseer::~Overseer(){ + log_debugcpp("cum"); + deviceEnumerator->Release(); + for(unsigned long long i = 0; i < playbackDevices.size(); i++){ + delete(playbackDevices.at(i)); + } +} //int Overseer::getCaptureEndpoints(std::vector *captureEndpoints); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 90bc88b..f70e6d1 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,38 +1,105 @@ #pragma once -#define WIN32_LEAN_AND_MEAN +#include "msinclude.h" +#include "backsessionclasses.h" #include "global.h" -#include -#include +#include "contclasses.h" -#include -#include -#include -#include -#include - -#include -#include -#include -//#include -//#include -#include +class EndpointVolumeCallback; +class Session; class Endpoint { public: - Endpoint(IMMDevice* endpoint); - void setVolume(float volume); - float getVolume(); - LPWSTR getName(); - //~Endpoint(); + Endpoint(IMMDevice* endpoint, uint64_t idx); + //todo: how to forward declare delegate constructors? + Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; + void reloadEndpointChannels(); + uint64_t getIndex(); + void setIndex(uint64_t idx); + void setVolume(NGuid guid, int channel, float volume); + uint32_t getChannelCount(); + float getVolume(int channel); + void setMute(NGuid guid, bool muted); + bool getMute(); + void setState(uint8_t state); + size_t getState(); + Roles getRoles(); + void setRoles(Roles role); + void assignRoles(Roles role); + void removeRoles(Roles role); + void setFlow(); + Flows getFlow(); + std::wstring getId(); + std::wstring getName(); + + void setVolumeCallback(EndpointVolumeCallback *epc); + void removeVolumeCallback(EndpointVolumeCallback *epc); + + /* sessions */ + std::vector getSessions(); + size_t getSessionCount(); + void addSession(Session* session); + void registerNewSessionNotification(EndpointNewSessionCallback* ensc); + void unregisterNewSessionNotification(EndpointNewSessionCallback* ensc); + + ~Endpoint(); private: + void inline activateEndpointVolume(); + void inline activateEndpointSessions(); + + std::vector endpointSessions; + uint32_t channelCount = 0; IMMDevice* endpoint; - IAudioEndpointVolume *endpointVolume ; + IAudioSessionManager2 *sessionManager; + Flows flow; + IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; - LPWSTR friendlyName; - // LPWSTR endpointID = NULL; + std::wstring friendlyName; + std::wstring endpointId; + unsigned long endpointState; + Roles endpointRoles = (Roles)0; + uint64_t idx; + /* Not implemented in llvm-mingw. Sad! + * IAudioMeterInformation *endpointPeakMeter = nullptr; + */ +}; + +class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { + + public: + EndpointVolumeCallback(Endpoint* ep); + + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA update); + //~EndpointVolumeCallback(); + + private: + ULONG ref = 1; + Endpoint* ep; +}; + +class EndpointSituationCallback : public IMMNotificationClient { + public: + //EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId); + HRESULT OnDeviceAdded(LPCWSTR pwstrDeviceId); + HRESULT OnDeviceRemoved(LPCWSTR pwstrDeviceId); + HRESULT OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); + HRESULT OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key); + + void fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices, std::vector captureDevices); + private: + ULONG ref = 1; + IMMDeviceEnumerator *deviceEnumerator; + std::vector playbackDevices; + std::vector captureDevices; }; class Overseer { @@ -40,21 +107,44 @@ class Overseer { public: Overseer(); std::vector getPlaybackEndpoints(); - void reloadEndpoints(); + std::vector getCaptureEndpoints(); + + void reloadEndpoints(Flows flow); + Endpoint* addEndpoint(std::wstring endpointId, /* out */ Flows* flow); + NGuid getGuid(); + //void setEndpointStatusCallback(); + //void setEndpointStatusCallback(); + //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); //int getCaptureEndpoints(std::vector *captureEndpoints); //IMMDeviceEnumerator** setOrigin(); - //~Overseer(); + ~Overseer(); private: - unsigned int numPlaybackEndpoints; + NGuid guid; + IMMDeviceEnumerator *deviceEnumerator; + EndpointSituationCallback epsc; + //IPolicyConfig *policyConfig; std::vector playbackDevices; + std::vector captureDevices; void initCOMLibrary(); //IMMDeviceCollection *deviceCollection; //int numCaptureEndpoints; //std::vector *captureDevices; }; +class EndpointNewSessionCallback : public IAudioSessionNotification { + public: + EndpointNewSessionCallback(EndpointHandler *eph); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnSessionCreated(IAudioSessionControl *NewSession); + + private: + ULONG ref = 1; + EndpointHandler *eph; +}; diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp new file mode 100644 index 0000000..ced7fb1 --- /dev/null +++ b/src/back/backsessionclasses.cpp @@ -0,0 +1,282 @@ +#include "backsessionclasses.h" +#include "backfuncs.h" + +SessionStateCallback::SessionStateCallback(SessionHandler *sh) { + this->sh = sh; +} + +ULONG SessionStateCallback::AddRef() { + return InterlockedIncrement(&ref); +} + +ULONG SessionStateCallback::Release() { + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT SessionStateCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioSessionNotification) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT SessionStateCallback::OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContex) { + return S_OK; +} + +HRESULT SessionStateCallback::OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext) { + sh->getVolumeInfo()->muted = NewMute; + sh->getVolumeInfo()->mainVolume = NewVolume; + sh->getVolumeInfo()->caller = GUIDToNGuid((LPGUID)EventContext); + /* + * if (NewMute) + * { + * printf("MUTE\n"); + * } + * else + * { + * printf("Volume = %d percent\n", + * (UINT32)(100*NewVolume + 0.5)); + * } + */ + return S_OK; +} + +HRESULT SessionStateCallback::OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { + SessionState newState;// = sh->getState(); + switch (NewState) { + case AudioSessionStateActive: + newState = SessionState::ACTIVE; + break; + case AudioSessionStateInactive: + newState = SessionState::INACTIVE; + break; + case AudioSessionStateExpired: + newState = SessionState::EXPIRED; + break; + } + + sh->reviseSessionShowing(newState); + return S_OK; +} + +HRESULT SessionStateCallback::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) { + sh->setState(SessionState::DISCONNECTED); + sh->reviseSessionShowing(SessionState::DISCONNECTED); + return S_OK; +} + +Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx) { + this->ep = ep; + this->sessionControl = sessionControl; + this->idx = idx; + + AudioSessionState msState; + sessionControl->GetState(&msState); + switch (msState) { + case AudioSessionState::AudioSessionStateActive: + this->sessionState = SessionState::ACTIVE; + break; + case AudioSessionState::AudioSessionStateInactive: + this->sessionState = SessionState::INACTIVE; + break; + case AudioSessionState::AudioSessionStateExpired: + this->sessionState = SessionState::EXPIRED; + break; + } + + sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); + DWORD pid; + sessionControl->GetProcessId(&pid); + if (sessionControl->IsSystemSoundsSession() == S_OK) + this->sessionName = std::wstring(LSTRING_SYSTEM_SOUNDS); + else { + LPWSTR sessionDisplayName; + this->sessionControl->GetDisplayName(&sessionDisplayName); + if (!wcscmp(sessionDisplayName, L"")) + this->sessionName = this->fetchProcessName(pid); + else + this->sessionName = std::wstring(sessionDisplayName); + CoTaskMemFree(sessionDisplayName); + } +} + +float Session::getVolume(int channel){ + float volume; + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(sessionVolume->GetMasterVolume(&volume))) { /* log_debugcpp("si") */;} + } else { + return 0.0; + //if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { /* log_debugcpp("si"); */} + } + return volume; +} + +/* + * uint32_t Endpoint::getChannelCount(){ + * return (uint32_t)channelCount; + * } + */ + +std::wstring Session::getName() { + return sessionName; +} + +bool Session::getMute() { + BOOL mut; + if(FAILED(sessionVolume->GetMute(&mut))) { /* TIP: Below */ } + bool mute = (bool)mut; + return mute; +} + +void Session::setVolume(NGuid guid, int channel, float volume) { + //TIP: There used to be log messages here. Now, it's a ghost town. + GUID tempMsGuid = NGuidToGUID(guid); + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(sessionVolume->SetMasterVolume(volume, &tempMsGuid))) {}; + } else { + //if(FAILED(sessionVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) {}; + } +} + +void Session::setIndex(size_t idx) { + this->idx = idx; +} + +void Session::setMute(NGuid guid, bool muted) { + GUID tempMsGuid = NGuidToGUID(guid); + if(FAILED(sessionVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"SessionVolume null?")); }; +} + +std::wstring Session::fetchProcessName(DWORD pid) { + /* + * https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot + * https://stackoverflow.com/questions/11843368/how-to-get-process-description + */ + + /* Executable path retrieval */ + std::wstring exePath = L""; + + HANDLE processList = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); + if (processList == INVALID_HANDLE_VALUE) { + log_wdebugcpp(L"aye no procname."); + return exePath; + } + + MODULEENTRY32W me32w; + me32w.dwSize = sizeof(MODULEENTRY32W); + if(Module32FirstW(processList, &me32w)) { + do { + if (me32w.th32ProcessID == pid) { + exePath = std::wstring(me32w.szExePath); + break; + /* + * However, if the calling process is a 32-bit process, you must call the + * QueryFullProcessImageName function to retrieve the full path of the + * executable file for a 64-bit process. + */ + } + } while(Module32NextW(processList, &me32w)); + } + CloseHandle(processList); + + /* File description retrieval */ + struct LANGANDCODEPAGE { + WORD wLanguage; + WORD wCodePage; + } *translationArray; + + DWORD filler; + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(exePath.c_str(), &filler); + if (!fileVersionInfoSize) return exePath; + + void* fileVersionInfo = malloc(fileVersionInfoSize); + if(!GetFileVersionInfoW(exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) + return exePath; + + UINT translationArrayLen = 0; + if (!VerQueryValueW(fileVersionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&translationArray, &translationArrayLen)) + return exePath; + + bool match = false; + for (UINT i = 0; i < (translationArrayLen / sizeof(LANGANDCODEPAGE)); i++) { + wchar_t fileDescriptionKey[256]; + LANGID defaultUILanguage = GetUserDefaultUILanguage(); + if (defaultUILanguage != translationArray[i].wLanguage) + continue; + + match = true; + wchar_t* fileDescription = NULL; + UINT fileDescriptionSize = 0; + swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", + translationArray[i].wLanguage, translationArray[i].wCodePage); + if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { + exePath = std::wstring(fileDescription); + } + } + + if (!match && 1 <= (translationArrayLen / sizeof(LANGANDCODEPAGE))) { + wchar_t fileDescriptionKey[256]; + +wchar_t* fileDescription = NULL; + UINT fileDescriptionSize = 0; + swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", + translationArray[0].wLanguage, translationArray[0].wCodePage); + if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { + exePath = std::wstring(fileDescription); + } + } + + free(fileVersionInfo); + return exePath; +} + +//todo: conflicting names. change callback name +void Session::setState(SessionState state) { + sessionState = state; +} + +SessionState Session::getState() { + return sessionState; +} + +void Session::setStateCallback(SessionStateCallback *ssc){ + sessionControl->RegisterAudioSessionNotification((IAudioSessionEvents*) ssc); +} + +void Session::removeStateCallback(SessionStateCallback *ssc){ + sessionControl->UnregisterAudioSessionNotification((IAudioSessionEvents*) ssc); +} + +Session::~Session() { + sessionControl->Release(); + sessionVolume->Release(); +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h new file mode 100644 index 0000000..e6c8490 --- /dev/null +++ b/src/back/backsessionclasses.h @@ -0,0 +1,57 @@ +#pragma once +#include "msinclude.h" + +#include "global.h" +#include "contclasses.h" + +class Endpoint; + +class SessionStateCallback : public IAudioSessionEvents { + public: + SessionStateCallback(SessionHandler *sh); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnSessionCreated(IAudioSessionControl *NewSession); + HRESULT OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext); + HRESULT OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext); + HRESULT OnGroupingParamChanged( LPCGUID NewGroupingParam, LPCGUID EventContext); + HRESULT OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext); + HRESULT OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason); + HRESULT OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext); + HRESULT OnStateChanged(AudioSessionState NewState); + + private: + ULONG ref = 1; + SessionHandler *sh; +}; + +class Session { + + public: + Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx); + Session(Endpoint* ep, IAudioSessionControl2* sessionControl) : Session(ep, sessionControl, SIZE_MAX) {}; + void setVolume(NGuid guid, int channel, float volume); + float getVolume(int channel); + void setMute(NGuid guid, bool muted); + bool getMute(); + SessionState getState(); + void setState(SessionState state); + void setIndex(size_t idx); + std::wstring getName(); + + void setStateCallback(SessionStateCallback *ssc); + void removeStateCallback(SessionStateCallback *ssc); + ~Session(); + //uint32_t getChannelCount(); + + private: + std::wstring fetchProcessName(DWORD pid); + std::wstring sessionName; + SessionState sessionState; + Endpoint* ep; + IAudioSessionControl2* sessionControl = nullptr; + ISimpleAudioVolume* sessionVolume = nullptr; + size_t idx; +}; + diff --git a/src/back/ipolicyconfig.h b/src/back/ipolicyconfig.h new file mode 100644 index 0000000..2e10aab --- /dev/null +++ b/src/back/ipolicyconfig.h @@ -0,0 +1,192 @@ +// ---------------------------------------------------------------------------- +// PolicyConfig.h +// Undocumented COM-interface IPolicyConfig. +// Use for set default audio render endpoint +// @author EreTIk +// ---------------------------------------------------------------------------- + + +#pragma once + + +interface DECLSPEC_UUID("CA286FC3-91FD-42C3-8E9B-CAAFA66242E3") +IPolicyConfig10; + +interface DECLSPEC_UUID("00000000-0000-0000-C000-000000000046") +IPolicyConfig10_1; + +interface DECLSPEC_UUID("F8679F50-850A-41CF-9C72-430F290290C8") +IPolicyConfig7; + +/* interface DECLSPEC_UUID("568B9108-44BF-40B4-9006-86AFE5B5A620") */ +/* IPolicyConfigVista; */ + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") +IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") +CPolicyConfigClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigClient +// {870af99c-171d-4f9e-af0d-e63df40c2bc9} +// +// interface IPolicyConfig +// {f8679f50-850a-41cf-9c72-430f290290c8} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); +// +// @compatible: Windows 7 and Later +// ---------------------------------------------------------------------------- +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, + const PROPERTYKEY &, + PROPVARIANT * + ); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT * + ); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + PCWSTR wszDeviceId, + 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 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, */ +/* const PROPERTYKEY &, */ +/* PROPVARIANT * */ +/* ); */ + +/* virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( */ +/* PCWSTR, */ +/* const PROPERTYKEY &, */ +/* 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 */ +/* }; */ diff --git a/src/back/msinclude.h b/src/back/msinclude.h new file mode 100644 index 0000000..fc7beac --- /dev/null +++ b/src/back/msinclude.h @@ -0,0 +1,26 @@ +#pragma once + +#define _WIN32_WINNT 0x0A00 +#include + +//done by qt by def #define UNICODE + +#include +#include +#include +#include +#include +#include +#include +//#include + +#include +#include +#include +//#include +//#include +#include +#include +#include "ipolicyconfig.h" +#include +#include diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 30b2688..38c30b1 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,36 +1,337 @@ +#include "backlasses.h" #include "contclasses.h" +//TODO: pragma once -Overseer OverseerHandler::os; - -EndpointHandler::EndpointHandler(Endpoint *ept, QObject *parent) : QObject(parent) { - this->ept = ept; - eptName = QString::fromStdWString(ept->getName()); -} - -void EndpointHandler::setValue(int value){ - ept->setVolume((float)value / 100); -} - -QString EndpointHandler::getName(){ - return eptName; -} - -float EndpointHandler::getVolume(){ - return ept->getVolume(); -} - -Overseer OverseerHandler::getOverseer(){ - return os; -} - -OverseerHandler::OverseerHandler(QObject *parent) : QObject(parent) { +EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { + //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); + this->idx = idx; + this->flow = flow; + this->ep = (flow == Flows::FLOW_PLAYBACK ? osh->getPlaybackEndpoints().at(idx) : osh->getCaptureEndpoints().at(idx)); + + epc = new EndpointVolumeCallback(ep); + ensc = new EndpointNewSessionCallback(this); + this->callbackInfo.caller = osh->getGuid(); + ep->registerNewSessionNotification(ensc); + //epName = ep->getName(); + this->setBackEndpointVolumeCallbackInfoContent(this->getState()); + osh->pushBackEndpointHandler(this, flow); + if (this->flow == Flows::FLOW_PLAYBACK) { + for (int i = 0; i < this->getSessionCount(); i++) { + SessionHandler* sessionHandler = new SessionHandler(this, this->getSessions().at(i),i); + sessionHandlers.push_back(sessionHandler); + } + } } -std::vector* OverseerHandler::getEndpointHandlers(){ - return endpointHandlers; +void OverseerHandler::pushBackEndpointHandler(EndpointHandler* eph, Flows flow) { + if (eph == nullptr) return; + if (flow == Flows::FLOW_PLAYBACK) + this->playbackEndpointHandlers.push_back(eph); + else + this->captureEndpointHandlers.push_back(eph); + return; } -void OverseerHandler::setEndpointHandlers(std::vector *ephs){ - this->endpointHandlers = ephs; +void EndpointHandler::setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx){ + ephfv.visibility = state; + ephfv.frontIdx = frontIdx; +} + +uint64_t EndpointHandler::getFrontVisibilityIndex(){ + return ephfv.frontIdx; +} + +EndpointState EndpointHandler::getFrontVisibilityState(){ + return ephfv.visibility; +} + +Flows EndpointHandler::getFlow(){ + return ep->getFlow(); +} + +/* these two, currently unused. If I use them, I should feel bad. + * Endpoint* EndpointHandler::getEndpoint() { + * return this->ep; + * } + * + * EndpointVolumeCallback* EndpointHandler::getEndpointVolumeCallback() { + * return this->epc; + * } + */ + +BackEndpointVolumeCallbackInfo* EndpointHandler::getCallbackInfo(){ + return &this->callbackInfo; +} + +uint32_t EndpointHandler::getChannelCount(){ + return ep->getChannelCount(); +} + +void EndpointHandler::setIndex(uint64_t idx){ + this->idx = idx; +} + +uint64_t EndpointHandler::getIndex(){ + return idx; +} + +/* + * -1 for master volume + */ +void EndpointHandler::setVolume(NGuid guid, int channel, int value){ + if (channel == AudioChannel::CHANNEL_MAIN) + ep->setVolume(guid, channel, (float)value / 100); + else ep->setVolume(guid, channel, (float)value / 100); +} + +void EndpointHandler::setMute(NGuid guid, bool muted){ + ep->setMute(guid, muted); +} + +std::wstring EndpointHandler::getName(){ + return ep->getName(); +} + +std::wstring EndpointHandler::getId(){ + return ep->getId(); +} + +float EndpointHandler::getVolume(int channel){ + return ep->getVolume(channel); +} + +bool EndpointHandler::getMute(){ + return ep->getMute(); +} + +size_t EndpointHandler::getState(){ + return ep->getState(); +} + +void EndpointHandler::setBackEndpointVolumeCallbackInfoContent(uint8_t state) { + if(state == EndpointState::ENDPOINT_ACTIVE) { + callbackInfo.muted = this->getMute(); + callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); + callbackInfo.channels = this->getChannelCount(); + ep->setVolumeCallback(epc); + callbackInfo.channelVolumes.resize(this->callbackInfo.channels); + for(uint32_t i = 0; i < this->getChannelCount(); i++){ + callbackInfo.channelVolumes.at(i) = this->getVolume(i); + } + } +} + +void EndpointHandler::setState(uint8_t state){ + ep->setState(state); + this->setBackEndpointVolumeCallbackInfoContent(state); +} + +void EndpointHandler::setState(uint8_t state, uint64_t index){ + ep->setState(state); + this->setFrontVisibilityInfo((EndpointState)state, index); + this->setBackEndpointVolumeCallbackInfoContent(state); +} + +uint8_t EndpointHandler::getRoles(){ + return ep->getRoles(); +} + +void EndpointHandler::setRoles(Roles newRole){ + ep->setRoles(newRole); +} + +void EndpointHandler::assignRoles(Roles newRole){ + ep->assignRoles(newRole); +} + +void EndpointHandler::removeRoles(Roles newRole){ + ep->removeRoles(newRole); +} + +void EndpointHandler::setAddSessionWidgetFunction(std::function addSessionWidget) { + this->addSessionWidget = addSessionWidget; +} + +void EndpointHandler::setRemoveSessionWidgetFunction(std::function removeSessionWidget) { + this->removeSessionWidget = removeSessionWidget; +} + +/* sessions */ +size_t EndpointHandler::getSessionCount() { + return ep->getSessionCount(); +} + +std::vector EndpointHandler::getSessions(){ + return ep->getSessions(); +} + +std::vector EndpointHandler::getSessionHandlers(){ + return this->sessionHandlers; +} + +Endpoint* EndpointHandler::getEndpoint() { + return this->ep; +} + +void EndpointHandler::addSessionSendFront(Session* session) { + ep->addSession(session); + + SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); + sessionHandlers.push_back(sessionHandler); + this->addSessionWidget(sessionHandler); +} + +void EndpointHandler::sendSessionToFront(SessionHandler* sh) { + this->addSessionWidget(sh); +} + +void EndpointHandler::removeSessionFromFront(SessionHandler* sh) { + this->removeSessionWidget(sh); +} + +EndpointHandler::~EndpointHandler() { + ep->removeVolumeCallback(epc); + ep->unregisterNewSessionNotification(ensc); + epc->Release(); + delete ep; +} + +OverseerHandler::OverseerHandler() { + this->os = new Overseer(); +} + +std::vector OverseerHandler::getPlaybackEndpoints() { + return this->os->getPlaybackEndpoints(); +} + +std::vector OverseerHandler::getCaptureEndpoints() { + return this->os->getCaptureEndpoints(); +} + +std::vector OverseerHandler::getPlaybackEndpointHandlers(){ + return playbackEndpointHandlers; +} + +std::vector OverseerHandler::getCaptureEndpointHandlers(){ + return captureEndpointHandlers; +} + +uint64_t OverseerHandler::getPlaybackEndpointsCount(){ + return this->os->getPlaybackEndpoints().size(); +} + +uint64_t OverseerHandler::getCaptureEndpointsCount(){ + return this->os->getCaptureEndpoints().size(); +} + +void OverseerHandler::reloadEndpointHandlers(){ + //todo: add capture + + //std::vector* ephs = new std::vector; + log_debugcpp("Playback VSize: " + std::to_string(this->getPlaybackEndpointsCount())); + + for(uint64_t i = 0; i < this->getPlaybackEndpointsCount(); i++){ + log_debugcpp("Creating Playback handler " + std::to_string(i)); + + EndpointHandler* ephexx = new EndpointHandler(i, Flows::FLOW_PLAYBACK); + //this->playbackEndpointHandlers.push_back(ephexx); + log_debugcpp("Created Playback handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->playbackEndpointHandlers.size())); + + } + + log_debugcpp("Capture VSize: " + + std::to_string(this->getCaptureEndpointsCount())); + + for(uint64_t i = 0; i < this->getCaptureEndpointsCount(); i++){ + log_debugcpp("Creating Capture handler " + std::to_string(i)); + + /* + * if(i < (this->captureEndpointHandlers.size()) && + * this->captureEndpointHandlers.at(i) != nullptr) + * delete captureEndpointHandlers.at(i); + */ + + EndpointHandler* ephoo = new EndpointHandler(i, Flows::FLOW_CAPTURE); + //this->captureEndpointHandlers.push_back(ephoo); + log_debugcpp("Created Capture handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->captureEndpointHandlers.size())); + + /* + * if (i >= this->captureEndpointHandlers.size()) + * captureEndpointHandlers.push_back(eph); + * else captureEndpointHandlers.at(i) = eph; + */ + } + //setEndpointHandlers(ephs); +} + +EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId, /* out */ Flows *flow = nullptr){ + Flows localFlow; + Endpoint* newEp = this->os->addEndpoint(endpointId, &localFlow); + + uint64_t ephIdx = (localFlow == Flows::FLOW_PLAYBACK ? this->getPlaybackEndpointsCount() : this->getCaptureEndpointsCount()) - 1; + + EndpointHandler* newEph = new EndpointHandler(ephIdx, localFlow); + // std::vector getPlaybackEndpointHandlers(); + //std::vector getCaptureEndpointHandlers(); + if (flow != nullptr) *flow = localFlow; + return newEph; +} + +NGuid OverseerHandler::getGuid() { + return this->os->getGuid(); +} + +void OverseerHandler::setChangeFrontDefaultsFunction(std::function changeFrontDefaults){ + this->changeFrontDefaults = changeFrontDefaults; +} + +void OverseerHandler::changeFrontDefaultsCallback(Roles role, std::wstring endpointId) { + this->changeFrontDefaults(role, endpointId); +} + +void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { + std::vector allHandlers; + allHandlers.insert(allHandlers.end(), this->captureEndpointHandlers.begin(), this->captureEndpointHandlers.end()); + allHandlers.insert(allHandlers.end(), this->playbackEndpointHandlers.begin(), this->playbackEndpointHandlers.end()); + EndpointHandler* eph = nullptr; + for (auto loopEph : allHandlers) { + if (loopEph->getId() == endpointId) { + eph = loopEph; + break; + } + } + + //debug + Flows flow; + if (!eph) { + if (state ^ EndpointState::ENDPOINT_ACTIVE) return; + //return; + //flow = Flows::FLOW_CAPTURE; + eph = osh->addEndpoint(endpointId, &flow); + } else + flow = eph->getFlow(); + + //todo: mic done but disabled. Tab-kun will come... + if (flow == Flows::FLOW_CAPTURE) return; + + + if(eph && EndpointState::ENDPOINT_ACTIVE & state) { + this->addEndpointWidget(eph); + } else if (eph && eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ + this->removeEndpointWidget(eph->getFrontVisibilityIndex()); + } + return; +} + +void OverseerHandler::setAddEndpointWidgetFunction(std::function addEndpointWidget){ + this->addEndpointWidget = addEndpointWidget; +} + +void OverseerHandler::setRemoveEndpointWidgetFunction(std::function removeEndpointWidget){ + this->removeEndpointWidget = removeEndpointWidget; +} + +void OverseerHandler::setEndpointHandlers(std::vector ephs){ + this->playbackEndpointHandlers = ephs; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 647ca1d..25fcaea 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,49 +1,138 @@ #pragma once -#include + #include "global.h" -#include "backlasses.h" - - -class EndpointHandler : public QObject { - Q_OBJECT - -public: - EndpointHandler(Endpoint *ept, QObject *parent = nullptr); - QString getName(); - float getVolume(); - -private: - Endpoint *ept; - QString eptName; - //QSlider *slidy; - -public slots: - void setValue(int value); - - -//signals: +#include "contsessionclasses.h" +//#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) +//#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) +class EndpointWidget; +class Endpoint; +class EndpointVolumeCallback; +class Overseer; +class SessionHandler; +class EndpointNewSessionCallback; +struct BackEndpointVolumeCallbackInfo { + NGuid caller; + bool muted; + float mainVolume; + size_t channels; + std::vector channelVolumes; }; +class EndpointHandler { + +public: + EndpointHandler(uint64_t idx, Flows flow); + void setBackEndpointVolumeCallbackInfoContent(uint8_t state); -class OverseerHandler : public QObject { - Q_OBJECT + //these two, currently unused. If I use them, I should feel bad. + //EndpointVolumeCallback* getEndpointVolumeCallback(); + //Endpoint* getEndpoint(); + + //std::wstring epName; + BackEndpointVolumeCallbackInfo* getCallbackInfo(); + uint32_t getChannelCount(); + + void setIndex(uint64_t idx); + uint64_t getIndex(); + void setVolume(int channel, float volume); + + std::wstring getName(); + std::wstring getId(); + + void setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx); + uint64_t getFrontVisibilityIndex(); + EndpointState getFrontVisibilityState(); + + Flows getFlow(); + float getVolume(int channel); + bool getMute(); + size_t getState(); + uint8_t getRoles(); + void setRoles(Roles newRole); + void assignRoles(Roles newRole); + void removeRoles(Roles newRole); + + void setVolume(NGuid guid, int channel, int value); + void setMute(NGuid guid, bool muted); + void setState(uint8_t state); + void setState(uint8_t state, uint64_t idx); + + /* sessions */ + size_t getSessionCount(); + std::vector getSessionHandlers(); + void createNewSession(); + Endpoint* getEndpoint(); + + /*Session*/ + void addSessionSendFront(Session* session); + void setAddSessionWidgetFunction(std::function addSessionWidget); + void setRemoveSessionWidgetFunction(std::function removeSessionWidget); + void sendSessionToFront(SessionHandler* sh); + void removeSessionFromFront(SessionHandler* sh); + + ~EndpointHandler(); +private: + std::vector getSessions(); + + uint64_t idx; + Endpoint *ep = nullptr; + EndpointVolumeCallback *epc = nullptr; + Flows flow; + BackEndpointVolumeCallbackInfo callbackInfo; + struct EndpointHandlerFrontVisibility { + EndpointState visibility = EndpointState::ENDPOINT_ALL; + uint64_t frontIdx = INT_MAX; + }; + EndpointHandlerFrontVisibility ephfv; + EndpointNewSessionCallback* ensc; + std::vector sessionHandlers; + std::function addSessionWidget; + std::function removeSessionWidget; + //QSlider *slidy; +}; + +class OverseerHandler { public: - OverseerHandler(QObject *parent = nullptr); - void setEndpointHandlers(std::vector *ephs); - std::vector* getEndpointHandlers(); - static Overseer getOverseer(); + OverseerHandler(); + void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); + void changeFrontDefaultsCallback(Roles role, std::wstring endpointId); + + //void setReviseEndpointShowingFunction(std::function reviseEndpointShowing); + void reviseEndpointShowing(std::wstring endpointId, EndpointState state); + void setRemoveEndpointWidgetFunction(std::function removeEndpointWidget); + void setAddEndpointWidgetFunction(std::function addEndpointWidget); + + void setEndpointHandlers(std::vector ephs); + std::vector getPlaybackEndpointHandlers(); + std::vector getCaptureEndpointHandlers(); + std::vector getPlaybackEndpoints(); + std::vector getCaptureEndpoints(); + void pushBackEndpointHandler(EndpointHandler* eph, Flows flow); + uint64_t getPlaybackEndpointsCount(); + uint64_t getCaptureEndpointsCount(); + void reloadEndpointHandlers(); + EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); + NGuid getGuid(); + + /* + * void setSessionVolumeCallback(std::function changeSessionVolume); + * void setSessionVolume(float newValue, ); + */ private: - static Overseer os; - std::vector *endpointHandlers; - //QSlider *slidy; - - //public slots: - //void setValue(int value); - - + Overseer *os; + std::vector playbackEndpointHandlers; + std::vector captureEndpointHandlers; + std::function changeFrontDefaults; + std::function removeEndpointWidget; + std::function addEndpointWidget; + /* Session's */ + std::function changeSessionVolume; + //std::function updateFrontVolumeCallback; + //std::function updateFrontMuteCallback; + }; diff --git a/src/cont/contsessionclasses.cpp b/src/cont/contsessionclasses.cpp new file mode 100644 index 0000000..155c0e1 --- /dev/null +++ b/src/cont/contsessionclasses.cpp @@ -0,0 +1,79 @@ +#include "contsessionclasses.h" +#include "backsessionclasses.h" + +SessionHandler::SessionHandler(EndpointHandler* eph, Session* session, size_t idx) { + this->eph = eph; + this->idx = idx; + this->session = session; + + this->svi.mainVolume = session->getVolume(AudioChannel::CHANNEL_MAIN); + this->svi.muted = session->getMute(); + this->svi.caller = osh->getGuid(); + + ssc = new SessionStateCallback(this); + this->session->setStateCallback(ssc); +} + +void SessionHandler::setVolume(NGuid guid, int channel, int value){ + if (channel == AudioChannel::CHANNEL_MAIN) + session->setVolume(guid, channel, (float)value / 100); + else session->setVolume(guid, channel, (float)value / 100); +} + +float SessionHandler::getVolume(int channel){ + return session->getVolume(channel); +} + +void SessionHandler::setMute(NGuid guid, bool muted){ + session->setMute(guid, muted); +} + +std::wstring SessionHandler::getName(){ + return session->getName(); +} + +bool SessionHandler::getMute(){ + return session->getMute(); +} + +void SessionHandler::setFrontIndex(uint64_t frontIdx) { + this->frontIdx = frontIdx; +} + +uint64_t SessionHandler::getFrontIndex() { + return frontIdx; +} + +SessionVolumeInfo* SessionHandler::getVolumeInfo() { + return &svi; +} + +SessionState SessionHandler::getState() { + return session->getState(); +} + +void SessionHandler::setState(SessionState state) { + session->setState(state); +} + +void SessionHandler::reviseSessionShowing(SessionState state) { + SessionState currentState = this->getState(); + switch (currentState) { + case SessionState::ACTIVE: + case SessionState::INACTIVE: + if (state == SessionState::EXPIRED) { + eph->removeSessionFromFront(this); + } + break; + case SessionState::EXPIRED: + if (state == SessionState::ACTIVE || INACTIVE) { + eph->sendSessionToFront(this); + } + break; + case SessionState::DISCONNECTED: + if (frontIdx != INT_MAX) + eph->removeSessionFromFront(this); + break; + } +} + diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h new file mode 100644 index 0000000..51f620f --- /dev/null +++ b/src/cont/contsessionclasses.h @@ -0,0 +1,42 @@ +#pragma once + +#include "global.h" +//#include "contclasses.h" + +class EndpointHandler; +class Session; +class SessionStateCallback; + +struct SessionVolumeInfo { + //SessionVolumeInfo(bool muted, float mainVolume); + bool muted; + float mainVolume; + NGuid caller; + + //size_t channels; + //std::vector channelVolumes; +}; + +class SessionHandler { + public: + SessionHandler(EndpointHandler* eph, Session* session, size_t idx); + void setVolume(NGuid guid, int channel, int value); + float getVolume(int channel); + void setMute(NGuid guid, bool muted); + bool getMute(); + void setFrontIndex(uint64_t frontIdx); + SessionState getState(); + void setState(SessionState state); + uint64_t getFrontIndex(); + std::wstring getName(); + void reviseSessionShowing(SessionState state); + SessionVolumeInfo* getVolumeInfo(); + + private: + SessionVolumeInfo svi; + EndpointHandler* eph; + Session* session; + SessionStateCallback* ssc; + size_t idx; + uint64_t frontIdx = INT_MAX; +}; diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..da6285c --- /dev/null +++ b/src/debug.h @@ -0,0 +1,47 @@ +#pragma once + +#if defined (QT_DEBUG) || defined (DEBUG) || defined (_DEBUG) + +template +std::bitset varToBitset(T info) { + std::bitset content(info); + return content; +} +#ifndef _WIN32 +#define log_debugcpp(str) do { \ + std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ + } while (0) + +#define log_wdebugcpp(str) do { \ + std::wcout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ + } while (0) +#else + +#include +#include +#define WIDE2(x) L##x +#define WIDE1(x) WIDE2(x) +#define WFILE WIDE1(__FILE__) +#define log_debugcpp(str) { \ + OutputDebugStringA(std::string("[DEBUG] (" + std::string(__FILE__) + ":" + std::to_string(__LINE__) + "): " + std::string(str) + "\n").c_str()); \ + } while (0) + +#define log_wdebugcpp(str) do { \ + OutputDebugStringW(std::wstring(L"[DEBUG] (" + std::wstring(WFILE) + L":" + std::to_wstring(__LINE__) + L"): " + std::wstring(str) +L"\n").c_str()); \ + } while (0) +#endif + +#define print_as_binary(len, type, info) varToBitset(info) + +#else +#define log_debugcpp(str) +#define log_wdebugcpp(str) +#define print_as_binary(len, type, info) +#endif + +/* Here as a quick reference, in case smthn similar is needed again */ +/* typedef void (EndpointWidget::*epwMuteFunc)(bool muted); */ +/* typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); */ +/* typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); */ +/* typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); */ + diff --git a/src/global.h b/src/global.h index c44b7e5..292ec06 100644 --- a/src/global.h +++ b/src/global.h @@ -1,4 +1,82 @@ #pragma once -#define log_debugcpp(str) do { \ - std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ - } while (0) + +#include +#include +#include +#include +#include +#include + +#include "debug.h" + +//TODO: Use tr();? QTranslator +#define STRING_MUTE "Mute" +#define STRING_UNMUTE "Unmute" +#define STRING_QUIT "Quit" +#define STRING_TITLE "Mixer Fachero" + +#define STRING_ROLE_CONSOLE "Console" +#define STRING_ROLE_MULTIMEDIA "Multimedia" +#define STRING_ROLE_COMMUNICATIONS "Communications" +#define STRING_ROLE_ALL "All" + +#define STRING_SYSTEM_SOUNDS "System Sounds" +#define LSTRING_SYSTEM_SOUNDS L"System Sounds" +//INIT BACK + +enum AudioChannel { + CHANNEL_LEFT = (1 << 0), + CHANNEL_RIGHT = (1 << 1), + CHANNEL_MAIN = ~0, +}; + +enum EndpointState { + ENDPOINT_ACTIVE = (1 << 0), + ENDPOINT_DISABLED = (1 << 1), + ENDPOINT_NOTPRESENT = (1 << 2), + ENDPOINT_UNPLUGGED = (1 << 3), + ENDPOINT_ALL = 0x0F +}; + +enum SessionState { + ACTIVE = (1 << 0), + INACTIVE = (1 << 1), + EXPIRED = (1 << 2), + DISCONNECTED = (1 << 3), + ALL = 0x0F +}; + +enum Flows { + FLOW_PLAYBACK = (1 << 0), + FLOW_CAPTURE = (1 << 1), + FLOW_BOTH = (1 << 2), +}; + +enum Roles { + ROLE_CONSOLE = (1 << 0), + ROLE_MULTIMEDIA = (1 << 1), + ROLE_COMMUNICATIONS = (1 << 2), + ROLE_ALL = 0x07, +}; + +struct NGuid { + //todo: still leaking? + uint32_t data1; + uint16_t data2; + uint16_t data3; + unsigned char data4[8]; + + + /* void freeData4(){ */ + /* int i = 0; */ + /* do{ */ + /* if(this->data4 + i != nullptr) free(data4 + i); */ + /* i++; */ + /* }while (i < 8); */ + /* } */ +}; + + +class OverseerHandler; +extern OverseerHandler *osh; + diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 38ae3f2..a9da261 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,53 +1,584 @@ #include "qtclasses.h" -MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : QMainWindow(parent) { - // setWindowState(Qt::WindowFullScreen); - // setCentralWidget(centralWidget); - widget = new QWidget(); - layout = new QGridLayout(); - - widget->setLayout(layout); - setCentralWidget(widget); - //layout->addWidget(pintas, 0, 0); - - setWindowTitle("slidea resbala nu c"); - - setEndpointHandlers(ephs); - for (unsigned int i = 0; i < this->ephs->size(); i++){ - QLabel *pintas = new QLabel(ephs->at(i)->getName()); - QSlider *teSlider = new QSlider(Qt::Horizontal); - teSlider->setFocusPolicy(Qt::StrongFocus); - teSlider->setTickPosition(QSlider::TicksBothSides); - teSlider->setTickInterval(5); - teSlider->setSingleStep(1); - teSlider->setRange(0,100); - float volume = ephs->at(i)->getVolume() * 100; - teSlider->setValue((int)volume); - log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); - layout->addWidget(pintas, i, 0); - layout->addWidget(teSlider, i, 1); - connect(teSlider, &QSlider::valueChanged, ephs->at(i), &EndpointHandler::setValue); - } +template +CustomWidgetEvent::CustomWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ + this->payload = payload; } -void MainWindow::setEndpointHandlers(std::vector *ephs){ - this->ephs = ephs; +void ExtendedCheckBox::customEvent(QEvent* ev) { + QEvent::Type tipo = ev->type(); + if (ev->type() == (QEvent::Type)CustomQEvent::EndpointDefaultChange) { + //todo: still prone to bugs; whack-a-mole to come + ev->setAccepted(true); + this->blockSignals(true); + if (this->isEnabled()) { + this->setCheckState(Qt::Checked); + this->setDisabled(true); + } else { + this->setDisabled(false); + this->setCheckState(Qt::Unchecked); + } + this->blockSignals(false); + return; + } + // Make sure the rest of events are handled + QCheckBox::customEvent(ev); +} + +SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) : QWidget(parent){ + //todo: based on qgridlayout, name+mute should be its own widget, same with channels + this->idx = idx; + this->sh = sh; + + layout = new QGridLayout(this); + //this->setLayout( + + muteButton = new QCheckBox(this); + mainLabel = new QLabel(QString::fromStdWString(sh->getName()), this); + mainSlider = new QSlider(Qt::Horizontal, this); + + mainSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainSlider->setFocusPolicy(Qt::StrongFocus); + mainSlider->setTickPosition(QSlider::TicksBothSides); + mainSlider->setTickInterval(5); + mainSlider->setSingleStep(1); + mainSlider->setRange(0,100); + + muteButton->setCheckState((sh->getMute() == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(sh->getMute() ? STRING_UNMUTE : STRING_MUTE); + float volume = sh->getVolume(AudioChannel::CHANNEL_MAIN) * 100; + mainSlider->setValue((int)volume); + log_debugcpp("SESSION SET WITH VOLUME " + std::to_string(volume)); + + //tip: would need to be new widget with layout in it + //mainMuteLayout = new QGridLayout(); + layout->addWidget(mainLabel, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(muteButton, 0, 1, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(mainSlider, 0, 2, 1, 2); + + //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? + connect(mainSlider, &QSlider::valueChanged, this,&SessionWidget::updateMainVolume); + connect(muteButton, &QCheckBox::stateChanged, this, (&SessionWidget::updateMute)); + + /* + * Session Volume Polling + */ + volumePoller = new QTimer(); + connect(volumePoller, &QTimer::timeout, [this, sh](){ + //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. + //todo: global + constexpr + ratio + const float roundingFactor = 0.005; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((sh->getVolumeInfo()->mainVolume + roundingFactor) * 100)); + muteButton->setCheckState((sh->getVolumeInfo()->muted == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(sh->getVolumeInfo()->muted ? STRING_UNMUTE : STRING_MUTE); + + //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); + + //TODO: el default = objcopy frees? + //Todo: like fr pregunta + sh->getVolumeInfo()->caller = osh->getGuid(); + mainSlider->blockSignals(false); + muteButton->blockSignals(false); + }); + volumePoller->start(10); +} + +void SessionWidget::updateMute(int checked){ + bool muted = (checked == 2 ? true : false); + this->sh->setMute(osh->getGuid(), muted); + this->muteButton->setText(this->sh->getMute() ? STRING_UNMUTE : STRING_MUTE); +} + +void SessionWidget::updateMainVolume(int newValue){ + this->sh->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); +} + +SessionWidget::~SessionWidget() { + volumePoller->stop(); + delete volumePoller; +} + +EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + //todo: based on qgridlayout, name+mute should be its own widget, same with channels + row = 0; + this->idx = idx; + this->eph = eph; + //todo: sussy + this->eph->setState(EndpointState::ENDPOINT_ACTIVE, idx); + + layout = new QGridLayout(this); + //this->setLayout(layout); + log_debugcpp("epw main layout parent: " + std::to_string((intptr_t)(layout->parent()))); + if (parent == nullptr) { log_debugcpp("ayooooo?"); } + + defaultRolesCheckBoxes = { + {Roles::ROLE_ALL, new ExtendedCheckBox(this)}, + {Roles::ROLE_CONSOLE, new ExtendedCheckBox(this)}, + {Roles::ROLE_MULTIMEDIA, new ExtendedCheckBox(this)}, + {Roles::ROLE_COMMUNICATIONS, new ExtendedCheckBox(this)} + }; + + /* + * Mute, main slider and label setup + */ + muteButton = new QCheckBox(this); + mainLabel = new QLabel(QString::fromStdWString(eph->getName()), this); + mainSlider = new QSlider(Qt::Horizontal, this); + + if (this->eph->getState() != EndpointState::ENDPOINT_ACTIVE) { + layout->addWidget(mainLabel, row, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); + return; + } + + //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); + mainSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainSlider->setFocusPolicy(Qt::StrongFocus); + mainSlider->setTickPosition(QSlider::TicksBothSides); + mainSlider->setTickInterval(5); + mainSlider->setSingleStep(1); + mainSlider->setRange(0,100); + + muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + float volume = eph->getVolume(AudioChannel::CHANNEL_MAIN) * 100; + mainSlider->setValue((int)volume); + log_debugcpp("ENDPOINT SET WITH VOLUME " + std::to_string(volume)); + + //tip: would need to be new widget with layout in it + //mainMuteLayout = new QGridLayout(); + layout->addWidget(mainLabel, row, 0, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(muteButton, row, 1, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(mainSlider, row, 2, 1, 2); + row++; + + //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? + connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); + connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); + + +/* + * Channel sliders setup + */ + uint32_t epChannelCount = eph->getChannelCount(); + for(uint32_t i = 0; i < epChannelCount && epChannelCount > 1; i++){ + QSlider* tmp = new QSlider(Qt::Horizontal); + QLabel* tmpLb = new QLabel(""); + tmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tmp->setTickInterval(5); + tmp->setSingleStep(1); + tmp->setRange(0,100); + + volume = eph->getVolume(i) * 100; + tmp->setValue((int) volume); + tmpLb->setText(QString::number(volume)); + this->channelSliders.push_back(tmp); + this->channelLabels.push_back(tmpLb); + layout->addWidget(tmp, row + 1, i); + layout->addWidget(tmpLb, row + 2, i); + + //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func + //this causes channel bar desync when back -> front. blocksignals below fix it. huh. + connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ + this->eph->setVolume(osh->getGuid(), i, newValue); + this->channelLabels.at(i)->setText(QString::number(newValue)); + }); + } + row += 3; + + /* + * Role ExtendedCheckBoxes setup + */ + + uint8_t assignedRoles = eph->getRoles(); + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(assignedRoles == Roles::ROLE_ALL ? Qt::Checked : Qt::Unchecked); + //todo duditas de & + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(assignedRoles == Roles::ROLE_ALL ? true : false); + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setText(STRING_ROLE_ALL); + + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setCheckState(assignedRoles & Roles::ROLE_CONSOLE ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setDisabled(assignedRoles & Roles::ROLE_CONSOLE ? true : false); + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setText(STRING_ROLE_CONSOLE); + + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setCheckState(assignedRoles & Roles::ROLE_MULTIMEDIA ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setDisabled(assignedRoles & Roles::ROLE_MULTIMEDIA ? true : false); + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setText(STRING_ROLE_MULTIMEDIA); + + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setCheckState(assignedRoles & Roles::ROLE_COMMUNICATIONS ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setDisabled(assignedRoles & Roles::ROLE_COMMUNICATIONS ? true : false); + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setText(STRING_ROLE_COMMUNICATIONS); + + connect(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_ALL); + }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_CONSOLE); + }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_MULTIMEDIA); + }); + + connect(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_COMMUNICATIONS); + }); + + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), row, 0); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), row, 1); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), row, 2); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), row, 3); + row++; +/* ----------------------------------------------------------- */ + + /* + * EndpointVolume Polling time + */ + timer = new QTimer(); + connect(timer, &QTimer::timeout, [this, eph](){ + //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. + //todo: global + constexpr + ratio + const float roundingFactor = 0.005; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((eph->getCallbackInfo()->mainVolume + roundingFactor) * 100)); + muteButton->setCheckState((eph->getCallbackInfo()->muted == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(eph->getCallbackInfo()->muted ? STRING_UNMUTE : STRING_MUTE); + for(uint32_t i = 0; i < eph->getCallbackInfo()->channels && eph->getChannelCount() > 1; i++){ + this->channelSliders.at(i)->blockSignals(true); + this->channelSliders.at(i)->setValue((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100)); + this->channelLabels.at(i)->setText(QString::number((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100))); + this->channelSliders.at(i)->blockSignals(false); + } + //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); + + //TODO: el default = objcopy frees? + //Todo: like fr pregunta + eph->getCallbackInfo()->caller = osh->getGuid(); + mainSlider->blockSignals(false); + muteButton->blockSignals(false); + }); + timer->start(10); + + /* First SessionWidget batch */ + for (size_t i = 0; i < eph->getSessionCount(); i++) { + SessionWidget* sessionWidget = new SessionWidget(i, eph->getSessionHandlers().at(i), this); + layout->addWidget(sessionWidget, row, 4); + row++; + sessionWidgets.push_back(sessionWidget); + eph->getSessionHandlers().at(i)->setFrontIndex(i); + } + + /* Add/Remove SessionWidget callback */ + eph->setAddSessionWidgetFunction([this](SessionHandler* sessionHandler) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetCreated, sessionHandler)); + }); + + eph->setRemoveSessionWidgetFunction([this](SessionHandler* sessionHandler) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetObsolete, sessionHandler)); + }); + + //todo parent? + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 4, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 6, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 6, 1); + log_debugcpp("ENDPOINT_WIDGETED"); +} + +void EndpointWidget::addSessionWidget(CustomWidgetEvent* ev){ + uint64_t index = this->sessionWidgets.size(); + SessionWidget* sw = new SessionWidget(index, ev->payload, this); + ev->payload->setFrontIndex(index); + this->layout->addWidget(sw, row, 4); + row++; + sessionWidgets.push_back(sw); + return; +} + +void EndpointWidget::removeSessionWidget(CustomWidgetEvent* ev){ + uint64_t i = ev->payload->getFrontIndex(); + this->sessionWidgets.at(i)->setParent(nullptr); + this->layout->removeWidget(sessionWidgets.at(i)); + delete sessionWidgets.at(i); + sessionWidgets.at(i) = nullptr; + ev->payload->setFrontIndex(INT_MAX); + //this->sessionWidgetsUpdateTimer->start(); + return; +} + +void EndpointWidget::customEvent(QEvent* ev) { + if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetCreated) { + ev->setAccepted(true); + this->addSessionWidget((CustomWidgetEvent*) ev); + } else if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetObsolete) { + ev->setAccepted(true); + this->removeSessionWidget((CustomWidgetEvent*) ev); + } + QWidget::customEvent(ev); +} + + +EndpointWidget::~EndpointWidget() { + timer->stop(); + delete timer; + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ALL, INT_MAX); +} + +void MainWindow::customEvent(QEvent* ev) { + if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { + ev->setAccepted(true); + this->removeEndpointWidget((CustomWidgetEvent*)ev); + } else if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetCreated) { + ev->setAccepted(true); + this->addEndpointWidget((CustomWidgetEvent*)ev); + } + QMainWindow::customEvent(ev); +} + +void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ + uint64_t i = ev->payload; + this->ews.at(i)->setParent(nullptr); + this->layout->removeWidget(ews.at(i)); + //uint64_t saisu = ews.size(); + //delete ews.at(index); + delete ews.at(i); + ews.at(i) = nullptr; + this->ewsUpdateTimer->start(); + return; +} + +void MainWindow::addEndpointWidget(CustomWidgetEvent* ev){ + EndpointWidget* epw = new EndpointWidget(this->ews.size(), ev->payload, widget); + this->layout->addWidget(epw); + ews.push_back(epw); + return; +} + +void MainWindow::reorderEndpointWidgetCollection() { + /* Flatten */ + size_t firstNullPosition = 0; + size_t ewsSize = ews.size(); + bool breakSorting = false; + + //todo: is all of gui really atomic by definition? im afraid of cutting through amazing add momentos, but I think I did my homework. Must check back. + for (size_t i = 0; i < ewsSize; i++) { + if (ews.at(i) == nullptr) { + for (size_t j = (i + 1); j < ewsSize; j++) { + + if (ews.at(j) != nullptr) { + ews.at(i) = ews.at(j); + ews.at(i)->setIndex(i); + ews.at(j) = nullptr; + break; + } + if (j == ewsSize - 1) { + firstNullPosition = i; + breakSorting = true; + } + + } + if (breakSorting) break; + } + } + ews.resize(firstNullPosition + 1); +} + +void EndpointWidget::updateMute(int checked){ + bool muted = (checked == 2 ? true : false); + this->eph->setMute(osh->getGuid(), muted); + this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); +} + +void EndpointWidget::updateMainVolume(int newValue){ + this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } /* - * void MainWindow::setPlotButton() { - * button = new QPushButton("push"), - * button->setCheckable(true); - * connect(button, SIGNAL(toggled(bool)), this, SLOT(toggled(bool))) - * QHBoxLayout *plotsLayout = new QHBoxLayout; - * plotsLayout->setSpacing(10); - * plotsLayout->addWidget(funPlot); - * QHBoxLayout *buttonsLayout = new QHBoxLayout ; - * buttonsLayout->addWidget(button); - * QVBoxLayout *widgetLayout = new QVBoxLayout; - * widgetLayout->addLayout(plotsLayout); - * widgetLayout->addLayout(buttonsLayout); - * setLayout(widgetLayout); - * ... - */ + * void EndpointWidget::updateVolume(uint32_t channel, float newValue){ + * //this->blockSignals(true); + * int newVal = newValue * 100; + * if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + * //TIP: Above + * //this->mainSlider->blockSignals(true); + * + * if(this->mainSlider->value() != newVal) { + * this->mainSlider->blockSignals(true); + * this->mainSlider->setValue(newVal); + * this->mainSlider->blockSignals(false); + * } + * return; + * } + * + * for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { + * if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { + * //this->channelSliders.at(i)->blockSignals(true); + * + * this->channelSliders.at(i)->setValue(newVal); + * this->channelLabels.at(i)->setText(QString::number((int)(newValue * 100))); + * + * //this->channelSliders.at(i)->blockSignals(false); + * } + * } + * + * //this->blockSignals(false); + * } + */ + +EndpointHandler* EndpointWidget::getEndpointHandler(){ + return this->eph; +} + +/* + * void EndpointWidget::updateFrontIndex(uint64_t index){ + * this->idx = index; + * } + */ + +void EndpointWidget::setIndex(uint64_t idx){ + this->idx = idx; + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, this->idx); +} + +uint64_t EndpointWidget::getIndex(){ + return idx; +} + +std::map EndpointWidget::getDefaultRolesWidgets() { + return defaultRolesCheckBoxes; +} + + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + // setWindowState(Qt::WindowFullScreen); + // setCentralWidget(centralWidget); + + /* + * Registering needed custom events + */ + QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); + QEvent::registerEventType(CustomQEvent::EndpointWidgetCreated); + QEvent::registerEventType(CustomQEvent::EndpointDefaultChange); + + ewsUpdateTimer = new QTimer(this); + widget = new QWidget(); + layout = new QGridLayout(); + trayIcon = new QSystemTrayIcon(); + trayIconMenu = new QMenu(); + trayIconMenuQuit = new QAction(STRING_QUIT); + + ewsUpdateTimer->setSingleShot(true); + ewsUpdateTimer->setInterval(ewsUpdateTimerFrequency); + connect(ewsUpdateTimer, &QTimer::timeout, this, &MainWindow::reorderEndpointWidgetCollection); + widget->setLayout(layout); + setCentralWidget(widget); + //layout->addWidget(pintas, 0, 0); + setWindowTitle(STRING_TITLE); + + reloadEndpointWidgets(); + + /* + * Tray Icon code + */ + trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayIconMenuQuit); + connect(trayIconMenuQuit, &QAction::triggered, qApp, &QCoreApplication::quit); + trayIcon->setIcon(QIcon(":/assets/notificationAreaIcon.png")); + setWindowIcon(QIcon(":/assets/notificationAreaIcon.png")); + //TODO: Extend qsystemtrayicon to change mouse click? + //show before setting tooltip required; smells like bug to me! + trayIcon->show(); + trayIcon->setToolTip(STRING_TITLE); + trayIcon->setContextMenu(trayIconMenu); + connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); + + /* + * Set of function callback definitons for EndpointSituationCallback + */ + osh->setChangeFrontDefaultsFunction([this](Roles role, std::wstring endpointId) { + for (auto epw : this->ews) { + /* + * Is this the new default endpoint? + */ + if (epw->getEndpointHandler()->getId() == endpointId) { + //not necessary to keep endpointState flags up to date right now, but updating it will allow for later config files / profiles + epw->getDefaultRolesWidgets().at(role)->blockSignals(true); + epw->getEndpointHandler()->assignRoles(role); + epw->getDefaultRolesWidgets().at(role)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + //epw->defaultRolesCheckBoxes.at(role)->postEnableChange(); + /* + * And were you THE default? + */ + if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + } + /* + * Are you the dethroned king? + */ + } else if (epw->getEndpointHandler()->getRoles() & role) { + /* + * And were you THE default up until now? + */ + if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + } + + epw->getDefaultRolesWidgets().at(role)->blockSignals(true); + //Same as before. ini-san will come... + epw->getEndpointHandler()->removeRoles(role); + epw->getDefaultRolesWidgets().at(role)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + + } + } + }); + + osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index)); + }); + + osh->setAddEndpointWidgetFunction([this](EndpointHandler* eph) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetCreated, eph)); + }); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if (!event->spontaneous() || !isVisible()) return; + + if (trayIcon->isVisible()) { + //todo: would be nice to show this to 1st time users; ini-san will come... + //this->trayIcon->showMessage("ini file calling","tratarte como un gilipollas la primera vez", QSystemTrayIcon::Information); + + hide(); + event->ignore(); + } +} + +void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + this->showNormal(); + break; + default: + break; + } +} + +void MainWindow::reloadEndpointWidgets() { + size_t i = 0; + for (size_t epwIndex = 0; i < (osh->getPlaybackEndpointHandlers().size()); i++) { + if (osh->getPlaybackEndpointHandlers().at(i)->getState() == EndpointState::ENDPOINT_ACTIVE){ + log_debugcpp("EPWidget creation"); + //osh->getPlaybackEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); + EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getPlaybackEndpointHandlers().at(i), widget); + epwIndex++; + //alfinal estoes solopara inicializarlmao + ews.push_back(epw); + layout->addWidget(epw, i, 0); + } + } + //todo:: tas aqui tirao, no me gustas y probablemente yo a ti tampoco + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); +} diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6ef82b0..b9c4fa8 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -1,34 +1,200 @@ #pragma once + #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include #include #include +#include + +#include +#include +#include +//#include + #include #include #include -//#include "global.h" -#include "contclasses.h" -//#include -//#include +#include +#include +#include +/* + * #else + * class QSlider; + * class QLabel; + * class QGridLayout; + * class QPushButton; + * class QWidget; + * class QMainWindow; + * #endif + */ +#include "global.h" +#include "contclasses.h" +//class EndpointHandler; + +/* + * class ToggleButton : public QAbstractButton { + * Q_OBJECT + * + * public: + * ToggleButton(QWidget *parent = nullptr); + * void checkStateSet(); + * bool hitButton(const QPoint &pos) const; + * void nextCheckState(); + * void changeEvent(QEvent *e) override; + * bool event(QEvent *e) override; + * void focusInEvent(QFocusEvent *e) override; + * void focusOutEvent(QFocusEvent *e) override; + * void keyPressEvent(QKeyEvent *e) override; + * void keyReleaseEvent(QKeyEvent *e) override; + * void mouseMoveEvent(QMouseEvent *e) override; + * void mousePressEvent(QMouseEvent *e) override; + * void mouseReleaseEvent(QMouseEvent *e) override; + * void paintEvent(QPaintEvent *e) override = 0; + * void timerEvent(QTimerEvent *e) override; + * ToggleButton(QWidget *parent = nullptr); + * }; + */ +enum CustomQEvent { + EndpointWidgetObsolete = 1001, + EndpointWidgetCreated = 1002, + EndpointDefaultChange = 1003, + SessionWidgetCreated = 1004, + SessionWidgetObsolete = 1005 +}; + +template +class CustomWidgetEvent : public QEvent { + +public: + CustomWidgetEvent(QEvent::Type type, T payload); + T payload; + +}; +//Q_DECLARE_METATYPE(EndpointWidgetEvent) + +class ExtendedCheckBox : public QCheckBox { + Q_OBJECT +protected: + void customEvent(QEvent* ev) override; + +public: + //c++11: this inherits all parent's constructors unconditionally + using QCheckBox::QCheckBox; + //alternative being calling parent ctor directly after declaring child ctor: + //B(int x) : A(x) { } +}; + + +class SessionWidget : public QWidget { +Q_OBJECT + +public: + SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent /* = nullptr */); + ~SessionWidget(); + +public slots: + void updateMainVolume(int newValue); + void updateMute(int checked); + +private: + QLabel *mainLabel = nullptr; + QSlider *mainSlider = nullptr; + uint64_t idx; + QGridLayout *layout = nullptr; + QCheckBox *muteButton = nullptr; + SessionHandler* sh; + QTimer* volumePoller = nullptr; +}; + +class EndpointWidget : public QWidget { +Q_OBJECT + +public: + EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); + + EndpointHandler* getEndpointHandler(); + std::map getDefaultRolesWidgets(); + + void setIndex(uint64_t idx); + uint64_t getIndex(); + void setVolume(int channel, float volume); + + ~EndpointWidget(); + //void updateMainVolume(float newValue); + //void updateVolume(uint32_t channel, float newValue); + //void updateMute(bool muted); + + //void populateEndpointWidget(EndpointHandler *eph); + //void setEndpointHandlers(std::vector *ephs); + +public slots: + void updateMainVolume(int newValue); + void updateMute(int checked); + +protected: + void customEvent(QEvent* ev) override; + +private slots: + void addSessionWidget(CustomWidgetEvent* ev); + void removeSessionWidget(CustomWidgetEvent* ev); + +private: + int row; + QCheckBox *muteButton = nullptr; + QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; + QSlider *mainSlider = nullptr; + std::vector channelSliders; + std::vector channelLabels; + QGridLayout *layout = nullptr; + QGridLayout *mainMuteLayout = nullptr; + std::map defaultRolesCheckBoxes; + + EndpointHandler* eph; + size_t defaultRolesVectorSize = 4; + QTimer* timer = nullptr; + uint64_t idx; + std::vector sessionWidgets; + //std::vector *ephs; + //std::vector *sliders; + + //signals: + //void valueChanged(int value); + +}; class MainWindow : public QMainWindow { Q_OBJECT //QWidget *centralWidget; public: - MainWindow(std::vector *ephs, QWidget *parent = nullptr); - void setEndpointHandlers(std::vector *ephs); + MainWindow(QWidget *parent = nullptr); + void reloadEndpointWidgets(); + +protected: + void closeEvent(QCloseEvent *event) override; + void customEvent(QEvent* ev) override; + +private slots: + void trayIconActivated(QSystemTrayIcon::ActivationReason reason); + void removeEndpointWidget(CustomWidgetEvent* ev); + void addEndpointWidget(CustomWidgetEvent* ev); + void reorderEndpointWidgetCollection(); + //TODO: destroy/empty existing EndpointWidgets + //void setEndpointHandlers(std::vector *ephs); private: - std::vector *ephs; - std::vector *sliders; + //std::vector *ephs; + std::vector ews; QWidget *widget; QGridLayout *layout; - //QLabel *pintas; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; + QAction *trayIconMenuQuit; + QTimer *ewsUpdateTimer; + static constexpr uint64_t ewsUpdateTimerFrequency = 500; //public slots: // void setEndpointHandlers(std::vector *ephs); diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 610f0b4..19cf627 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -1,42 +1,64 @@ -#include -#include - //#include //#include //#include + +//#define QTBLESSED #include -#include +#include +#include +#include +#include +//#include "contclasses.h" #include "qtclasses.h" -//TODO david #include "backlasses.h" +#include "global.h" - -//INIT BACK -OverseerHandler *osh = new OverseerHandler(); +OverseerHandler *osh = nullptr; QApplication* createApplication(int &argc, char *argv[]) { return new QApplication(argc, argv); } +bool isSingleInstanceRunning(QString appName) { + QLocalSocket socket; + socket.connectToServer(appName); + bool isOpen = socket.isOpen(); + socket.close(); + return isOpen; +} + +QLocalServer* startSingleInstanceServer(QString appName) { + QLocalServer* server = new QLocalServer; + server->setSocketOptions(QLocalServer::WorldAccessOption); + server->listen(appName); + return server; +} int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); - //INIT CONT - std::vector epts = OverseerHandler::getOverseer().getPlaybackEndpoints(); - std::vector* ephs = new std::vector; - for(unsigned int i = 0; i < epts.size(); i++){ - EndpointHandler *eph = new EndpointHandler(epts.at(i)); - ephs->push_back(eph); - } + //Check if running + //https://stackoverflow.com/questions/48060989/qt-show-application-if-currently-running + if (!isSingleInstanceRunning("Mixer")) + startSingleInstanceServer("Mixer"); + else exit(0); + + osh = new OverseerHandler(); + //qRegisterMetaType(); + + //INIT CONT + log_debugcpp("main init"); + osh->reloadEndpointHandlers(); + log_debugcpp("Reloaded endpoint handlers"); - osh->setEndpointHandlers(ephs); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); - MainWindow window = MainWindow(ephs); + MainWindow window = MainWindow(); //window.setEndpointHandlers(ephs); + QApplication::setQuitOnLastWindowClosed(false); app->setStyle("windowsvista"); window.show(); return app->exec(); } +