diff --git a/.gitignore b/.gitignore index fde2162..a4ce7f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build *.rdbg *.pdb +*.ps1 +*.exe Makefile Makefile.Debug Makefile.Release \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf1a785 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Mixer + +## Build + +* Toolchain: [llvm-mingw UCRT 20220906](https://github.com/mstorsjo/llvm-mingw/releases/tag/20220906). + +* Clone [this](https://code.qt.io/cgit/qt/qt5.git/tag/?h=v6.3.2) Qt branch + +* Build Qt from sources following [this](https://wiki.qt.io/Building_Qt_6_from_Git) guide and executing `configure.bat` as such: + +``` +..\qt6\configure.bat -prefix ..\install -static -debug -opensource -confirm-license -qt-zlib -qt-libpng -qt-webp -qt-libjpeg -qt-freetype -skip qt3d -skip qtactiveqt -skip qtandroidextras -skip qtcharts -skip qtconnectivity -skip qtdatavis3d -skip qtdoc -skip qtgamepad -skip qtlocation -skip qtlottie -skip qtmacextras -skip qtmultimedia -skip qtnetworkauth -skip qtpurchasing -skip qtquick3d -skip qtquick3dphysics -skip qtquicktimeline -skip qtremoteobjects -skip qtscript -skip qtsensors -skip qtwayland -skip qtwebglplugin -skip qtwebview -skip webengine -nomake examples -nomake tests +``` + +* Clone this repo and execute `bueno.bat`. \ No newline at end of file diff --git a/assets.qrc b/assets.qrc new file mode 100644 index 0000000..2206a8a --- /dev/null +++ b/assets.qrc @@ -0,0 +1,10 @@ + + + assets/selawk.ttf + assets/notificationAreaIcon.png + assets/style.qss + assets/logo.ico + assets/mute.svg + assets/unmute.svg + + diff --git a/assets/installer.ico b/assets/installer.ico new file mode 100644 index 0000000..b4e6f82 Binary files /dev/null and b/assets/installer.ico differ diff --git a/assets/installer.xcf b/assets/installer.xcf new file mode 100644 index 0000000..96669f1 Binary files /dev/null and b/assets/installer.xcf differ diff --git a/assets/logo.ico b/assets/logo.ico new file mode 100644 index 0000000..daac4a4 Binary files /dev/null and b/assets/logo.ico differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..259e37b Binary files /dev/null and b/assets/logo.png differ diff --git a/assets/logo.xcf b/assets/logo.xcf new file mode 100644 index 0000000..69cafac Binary files /dev/null and b/assets/logo.xcf 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/assets/selawk.ttf b/assets/selawk.ttf new file mode 100644 index 0000000..736bac3 Binary files /dev/null and b/assets/selawk.ttf differ diff --git a/assets/style.qss b/assets/style.qss new file mode 100644 index 0000000..4f6466c --- /dev/null +++ b/assets/style.qss @@ -0,0 +1,6 @@ +QMainWindow { background: rgba(100,100,100,100); } + + + + +QCheckBox:hover, QCheckBox:checked { color: white } \ No newline at end of file diff --git a/assets/uninstaller.ico b/assets/uninstaller.ico new file mode 100644 index 0000000..24278c2 Binary files /dev/null and b/assets/uninstaller.ico differ diff --git a/bueno.bat b/bueno.bat index cd190f0..540be60 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,2 +1,7 @@ -qmake -o build\Makefile .\qtest.pro -mingw32-make.exe -C .\build -f Makefile.Release +taskkill /F /IM "MixerQ.exe" +taskkill /F /IM "MixerQd.exe" +qmake -o build\Makefile .\qtest.pro +REM mingw32-make.exe -C .\build -f Makefile.Release +mingw32-make.exe -C .\build -f Makefile.Debug +REM makensis /DBUILDTYPE=release install\installer.nsi +REM makensis /DBUILDTYPE=debug install\installer.nsi diff --git a/install/installer.nsi b/install/installer.nsi new file mode 100644 index 0000000..7e62045 --- /dev/null +++ b/install/installer.nsi @@ -0,0 +1,265 @@ +;Auto versioning------------------------------- + + !makensis "/DBUILDTYPE=${BUILDTYPE} version.nsi" + !system "GetVersion.exe" + !include "Version.txt" + ;optional cleanup + !delfile "GetVersion.exe" + !delfile "Version.txt" + +;Includes-------------------------------- + + !include "MUI2.nsh" + !include "nsDialogs.nsh" + !include "LogicLib.nsh" + !include "${NSISDIR}\Contrib\Language files\English.nsh" + !include "${NSISDIR}\Contrib\Language files\Spanish.nsh" + ;!include "${NSISDIR}\Contrib\Language files\English.nsh" + + +;Defines---------------------------------- + !define MUI_LANGDLL_ALLLANGUAGES + !define MUI_UNICON "..\assets\uninstaller.ico" + !define MUI_ICON "..\assets\installer.ico" + + +;-------------------------------- +;General + + ;Name and file + !if ${BUILDTYPE} == "release" + Name "MixerQ" + !else + Name "MixerQd" + !endif + OutFile "..\build\bin\MixerQ-installer-${version}.exe" + + ;Get installation folder from registry if available + ;InstallDirRegKey HKCU "Software\Modern UI Test" "" + + Unicode True + Var Is_Admin + Var Install_Type + + ;Request application privileges for UAC. If admin is not available, only user-level install will be available + RequestExecutionLevel highest +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Pages + !insertmacro MUI_PAGE_WELCOME + !insertmacro MUI_PAGE_LICENSE "..\LICENSE.txt" + ;!insertmacro MULTIUSER_PAGE_INSTALLMODE + Page Custom InstallTargetPage + ;!insertmacro MUI_PAGE_COMPONENTS + !define MUI_PAGE_CUSTOMFUNCTION_PRE Skip_Directory_Func + !insertmacro MUI_PAGE_DIRECTORY + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_WELCOME + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + !insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "SpanishInternational" + + ;English---------------------------- + LangString Header_Title ${LANG_ENGLISH} "Configure Install" + LangString Header_Subtitle ${LANG_ENGLISH} "Customize install settings" + LangString Option_Scope ${LANG_ENGLISH} "Select for whom will $(^Name) be installed: " + LangString Scope_Machine ${LANG_ENGLISH} "All users" + LangString Scope_User ${LANG_ENGLISH} "Current user" + + ;Spanish/SpanishInternational---------------------------- + LangString Header_Title ${LANG_SPANISH} "Configurar instalación" + LangString Header_Subtitle ${LANG_SPANISH} "Elija los ajustes de la instalación" + LangString Option_Scope ${LANG_SPANISH} "$(^Name) será instalado para: " + LangString Scope_Machine ${LANG_SPANISH} "Todos los usuarios" + LangString Scope_User ${LANG_SPANISH} "Usuario actual" + + LangString Header_Title ${LANG_SPANISHINTERNATIONAL} "Configurar instalación" + LangString Header_Subtitle ${LANG_SPANISHINTERNATIONAL} "Elija los ajustes de la instalación" + LangString Option_Scope ${LANG_SPANISHINTERNATIONAL} "$(^Name) será instalado para: " + LangString Scope_Machine ${LANG_SPANISHINTERNATIONAL} "Todos los usuarios" + LangString Scope_User ${LANG_SPANISHINTERNATIONAL} "Usuario actual" + +;Functions------------------------------ + Function Skip_Directory_Func + ;StrCmp $Install_Type "user" dontSkip + Abort # skip the page + ;dontSkip: + FunctionEnd + + !macro ONINIT un + Function ${un}.onInit + ; The value of SetShellVarContext detetmines whether SHCTX is HKLM or HKCU + ; and whether SMPROGRAMS refers to all users or just the current + !insertmacro MUI_LANGDLL_DISPLAY + UserInfo::GetAccountType + Pop $0 + ${If} $0 == "Admin" + ; If we're an admin, default to installing to C:\Program Files + SetShellVarContext all + ; StrCpy $INSTDIR "$PROGRAMFILES64\$(^Name)" + StrCpy $Is_Admin "true" + ${Else} + ; If we're just a user, default to installing to ~\AppData\Local + SetShellVarContext current + ; StrCpy $INSTDIR "$LOCALAPPDATA\$(^Name)" + StrCpy $Is_Admin "false" + ${EndIf} + + ; ${If} $INSTDIR == "" + ; ; This only happens in the installer, because the uninstaller already knows INSTDIR + ; ReadRegStr $0 SHCTX "Software\${PRODUCT_NAME}" "" + + ; ${If} $0 != "" + ; ; If we're already installed, use the existing directory + ; StrCpy $INSTDIR "$0" + ; ${Else} + ; StrCpy $INSTDIR "$INSTDIR_BASE\${PRODUCT_NAME}" + ; ${Endif} + ; ${Endif} + FunctionEnd + !macroend + +!insertmacro ONINIT "" +!insertmacro ONINIT "un" + +;NSDialog InstallTarget Page definition--------------------------------- + +Function InstallTargetPage + !insertmacro MUI_HEADER_TEXT $(Header_Title) $(Header_Subtitle) + ;MessageBox MB_OK "Install type $Install_Type" + ;MessageBox MB_OK "Build type ${BUILDTYPE}" + nsDialogs::Create 1018 + Pop $0 + + FindWindow $0 "#32770" + GetDlgItem $1 $0 1 ;next/install button + SendMessage $1 ${WM_SETTEXT} 1 "STR:$(^InstallBtn)" + Pop $0 + + ${NSD_CreateLabel} 0 0 100% 10% $(Option_Scope) + Pop $3 + + ${NSD_CreateFirstRadioButton} 0 12% 40% 6% $(Scope_Machine) + Pop $1 + ${If} $Is_Admin == "false" + EnableWindow $1 0 + StrCpy $INSTDIR "$LOCALAPPDATA\$(^Name)" + ${Else} + SendMessage $1 ${BM_CLICK} "" "" ;Set default + StrCpy $INSTDIR "$PROGRAMFILES64\$(^Name)" + ${EndIf} + ${NSD_OnClick} $1 All_Users_Click + + ${NSD_CreateAdditionalRadioButton} 0 24% 40% 6% $(Scope_User) + Pop $2 + ${IfThen} $Is_Admin == "false" ${|} SendMessage $2 ${BM_CLICK} "" "" ${|} + ${NSD_OnClick} $2 Current_User_Click + + nsDialogs::Show +FunctionEnd + +Function All_Users_Click + Pop $0 + SetShellVarContext all + StrCpy $INSTDIR "$PROGRAMFILES64\$(^Name)" + StrCpy $Install_Type "machine" + ;${NSD_SetText} $0 "machine" + ;FindWindow $0 "#32770" + ;GetDlgItem $1 $0 1 ;next/install button + ;SendMessage $1 ${WM_SETTEXT} 1 "STR:$(^InstallBtn)" +FunctionEnd + +Function Current_User_Click + Pop $0 + SetShellVarContext current + StrCpy $INSTDIR "$LOCALAPPDATA\$(^Name)" + StrCpy $Install_Type "user" + ;${NSD_SetText} $0 "user" + ;FindWindow $0 "#32770" + ;GetDlgItem $1 $0 1 ;next/install button + ;SendMessage $1 ${WM_SETTEXT} 1 "STR:$(^NextBtn)" +FunctionEnd + +;Default section---------------------- +Section + SetRegView 64 + SetOutPath $INSTDIR + + !if ${BUILDTYPE} == "release" + File "..\build\bin\MixerQ.exe" + !else + File "..\build\bin\MixerQd.exe" + !endif + File "..\LICENSE.txt" + + ;Start menu shortcut + createDirectory "$SMPROGRAMS\$(^Name)" + createShortCut "$SMPROGRAMS\$(^Name)\$(^Name).lnk" "$INSTDIR\$(^Name).exe" + createShortCut "$SMPROGRAMS\$(^Name)\Uninstall$(^Name).lnk" "$INSTDIR\Uninstall$(^Name).exe" + + ;Store installation folder + WriteRegStr SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" "DisplayName" "$(^Name)" + WriteRegStr SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" "UninstallString" '"$INSTDIR\Uninstall$(^Name).exe"' + WriteRegDWORD SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" "NoModify" 1 + WriteRegDWORD SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" "NoRepair" 1 + WriteRegStr SHCTX "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "$(^Name)" "$INSTDIR\$(^Name)" + + ;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall$(^Name).exe" + +SectionEnd + + +;-------------------------------- +;Descriptions + + ; ;Language strings + ; LangString DESC_SecDummy ${LANG_ENGLISH} "A test section." + + ; ;Assign language strings to sections + ; !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + ; !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy) + ; !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + SetRegView 64 + + SetShellVarContext current + DeleteRegValue HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "$(^Name)" + DeleteRegKey HKCU "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" + Delete "$SMPROGRAMS\$(^Name)\$(^Name).lnk" + Delete "$SMPROGRAMS\$(^Name)\Uninstall$(^Name).lnk" + RMDir "$SMPROGRAMS\$(^Name)" + + ${If} $Is_Admin == "true" + SetShellVarContext all + DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" "$(^Name)" + DeleteRegKey HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$(^Name)" + Delete "$SMPROGRAMS\$(^Name)\$(^Name).lnk" + Delete "$SMPROGRAMS\$(^Name)\Uninstall$(^Name).lnk" + RMDir "$SMPROGRAMS\$(^Name)" + ${EndIf} + + Delete "$INSTDIR\$(^Name).exe" + Delete "$INSTDIR\LICENSE.txt" + Delete "$INSTDIR\Uninstall$(^Name).exe" + ;!define PRODUCT_UNINST_ROOT_KEY "HKLM" + RMDir "$INSTDIR" + +SectionEnd diff --git a/install/version.nsi b/install/version.nsi new file mode 100644 index 0000000..5e85c8c --- /dev/null +++ b/install/version.nsi @@ -0,0 +1,26 @@ +!if ${BUILDTYPE} == "release" + !define File "..\build\bin\MixerQ.exe" +!else + !define File "..\build\bin\MixerQd.exe" +!endif + +OutFile "GetVersion.exe" +SilentInstall silent +RequestExecutionLevel user ; don't write $EXEDIR\Version.txt with admin permissions and prevent invoking UAC + +Section + + ## Get file version + GetDllVersion "${File}" $R0 $R1 + IntOp $R2 $R0 / 0x00010000 + IntOp $R3 $R0 & 0x0000FFFF + IntOp $R4 $R1 / 0x00010000 + IntOp $R5 $R1 & 0x0000FFFF + StrCpy $R1 "$R2.$R3.$R4.$R5" + + ## Write it to a !define for use in main script + FileOpen $R0 "$EXEDIR\Version.txt" w + FileWrite $R0 '!define version "$R1"' + FileClose $R0 + +SectionEnd \ No newline at end of file diff --git a/qtest.pro b/qtest.pro index 7f7dbb8..42a1165 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,8 +1,28 @@ -CONFIG += debug console -QT += widgets -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 -#DESTDIR += "build" +TEMPLATE = app +QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -Werror=return-type +QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -v +CONFIG(release, debug|release) { + TARGET = MixerQ + DESTDIR = bin + VERSION = 0.9.0.0 + #QMAKE_CXXFLAGS += -O2 < Default. Modifying requires removing. +} else { + TARGET = MixerQd + DESTDIR = bin + VERSION = 0.9.0.1 + QMAKE_CXXFLAGS += -g -gcodeview -O0 + QMAKE_LFLAGS += -g -Wl,-pdb= +} + +LIBS += -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 -lpropsys -static -stdlib=libc++ -lunwind +DEFINES += QT_LOGGING_TO_CONSOLE=1 WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 +DEFINES_DEBUG += DEBUG + +QT += widgets network svg +INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\back\reimpl" "$$PWD\src\cont" +VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\back\reimpl" "$$PWD\src\cont" + +SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp backsessionclasses.cpp contclasses.cpp contsessionclasses.cpp settings.cpp +HEADERS += qtclasses.h backlasses.h backsessionclasses.h contclasses.h contsessionclasses.h global.h debug.h backfuncs.h ipolicyconfig.h msinclude.h meterslider.h qtvisuals.h settings.h +RESOURCES = assets.qrc +RC_ICONS += assets/logo.ico 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..74af6a0 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,40 +1,585 @@ -#include +#include "backlasses.h" +#include "backfuncs.h" -Endpoint::Endpoint(IMMDevice* ep){ - 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); - endpoint->OpenPropertyStore(STGM_READ, &properties); - PROPVARIANT pv; - properties->GetValue(PKEY_Device_FriendlyName , &pv); - friendlyName = pv.pwszVal; +using namespace Environment; + +EndpointNewSessionCallback::EndpointNewSessionCallback(EndpointHandler* eph){ + this->eph = eph; } -LPWSTR Endpoint::getName(){ +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; + + IAudioSessionControl2* sessionControl; + //ISimmpleAudioVolume* sessionVolume; + if (FAILED(NewSession->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl))) { log_wdebugcpp(L"no nueva sesion......"); }; + if (sessionControl) { + Session* newSession = new Session(this->eph->getEndpoint(), sessionControl); + + SessionThreadParams tp = { .eph = this->eph, .session = newSession, .isDelete = false }; + wait = true; + std::thread newSessionThread(&EndpointNewSessionCallback::createSessionThread, this, tp); + newSessionThread.detach(); + while(wait); + } + + return S_OK; +} + +void EndpointNewSessionCallback::createSessionThread(SessionThreadParams params) { + params.eph->addSessionSendFront(params.session); + this->wait = false; +} + +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; + + AUDIO_VOLUME_NOTIFICATION_DATA paramCopy; + memcpy(¶mCopy, pNotify, sizeof(AUDIO_VOLUME_NOTIFICATION_DATA)); + float* channelVolumes = (float*)malloc(pNotify->nChannels * sizeof(float)); + for (int i = 0; i < pNotify->nChannels; i++) { + channelVolumes[i] = pNotify->afChannelVolumes[i]; + } + wait = true; + std::thread updateVolumeThread(&EndpointVolumeCallback::updateVolumeInfo, this, paramCopy, channelVolumes); + updateVolumeThread.detach(); + while(wait); + return S_OK; +} + +void EndpointVolumeCallback::updateVolumeInfo(AUDIO_VOLUME_NOTIFICATION_DATA newVolume, float* channelVolumes) { + //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->handlersPlaybackMutex.lock(); + osh->handlersCaptureMutex.lock(); + osh->lockEndpoints(); + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ + = newVolume.guidEventContext.Data1; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ + = newVolume.guidEventContext.Data2; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ + = newVolume.guidEventContext.Data3; + for(int i = 0; i < 8 /* Data4 size */; i++){ + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = newVolume.guidEventContext.Data4[i]; + } + + //memcpy(&osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &newVolume.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 = newVolume.bMuted; + eph->getCallbackInfo()->mainVolume = newVolume.fMasterVolume; + eph->getCallbackInfo()->channels = newVolume.nChannels; + + + UINT j = 0; + //todo: do while here caused stack corruption; sus + while(j < newVolume.nChannels) { + if (flow & Flows::FLOW_PLAYBACK) + eph->getCallbackInfo()->channelVolumes[j] = channelVolumes[j]; + else + eph->getCallbackInfo()->channelVolumes[j] = channelVolumes[j]; + j++; + } + free(channelVolumes); + osh->unlockEndpoints(); + osh->handlersPlaybackMutex.unlock(); + osh->handlersCaptureMutex.unlock(); + wait = false; +} + +void EndpointVolumeCallback::reportFinished() { + this->wait = false; +} + +EndpointSituationCallback::EndpointSituationCallback(Overseer* os){ + this->os = os; +} + + +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; + if (!pwstrDeviceId) 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->roleBucketEntryCallback(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); + log_wdebugcpp(L"Endpoint state change for " + endpointId); + EndpointState newState; + switch (dwNewState) { + case DEVICE_STATE_ACTIVE: + newState = EndpointState::ENDPOINT_ACTIVE; + break; + case DEVICE_STATE_DISABLED: + newState = EndpointState::ENDPOINT_DISABLED; + break; + case DEVICE_STATE_NOTPRESENT: + newState = EndpointState::ENDPOINT_NOTPRESENT; + break; + case DEVICE_STATE_UNPLUGGED: + newState = EndpointState::ENDPOINT_UNPLUGGED; + break; + } + isEpStateChanging.exchange(true); + std::thread newEndpointThread(&OverseerHandler::reviseEndpointShowing, osh, + endpointId, newState); + newEndpointThread.detach(); + while(isEpStateChanging); + return S_OK; +} + +HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { + isEpStateChanging.exchange(true); + std::thread propertyThread(&Overseer::updateEndpointInfo, os, std::wstring(pwstrDeviceId)); + propertyThread.detach(); + while(isEpStateChanging); + return S_OK; +} + +void EndpointSituationCallback::reportFinishedStateChange() { + this->isEpStateChanging.exchange(false); + return; +} + +Endpoint::Endpoint(IMMDevice* ep, IPolicyConfig7* policyConfig, uint64_t idx) { + this->endpoint = ep; + this->idx = idx; + this->policyConfig = policyConfig; + /* + * It can't multiflag, it's that stupid. MS momento. + * Only shows most relevant flag according to MS, i.e. 0110 sends 0010 + */ + DWORD state; + if(FAILED(endpoint->GetState(&state))) {exit(-2);}; + this->endpointState = (EndpointState)state; + + if(this->endpointState == EndpointState::ENDPOINT_ACTIVE) { + activateEndpointVolume(); + + //if(FAILED(endpoint->Activate(__uuidof(IAudioClient), + // CLSCTX_ALL, NULL, (void**)&audioClient))) { log_debugcpp("audioclntbros..."); } + //audioClient->GetDevicePeriod(&defTime, &minTime); + + } + + //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); + this->updateName(); + this->setFlow(); + + reloadEndpointChannels(); +} + +void Endpoint::updateName() { + PROPVARIANT pv; + #define store_name(key, propvariant, wstr) do { \ + properties->GetValue(key, &propvariant); \ + if (pv.pwszVal == nullptr) wstr = L"Unnamed Not Present Endpoint"; \ + else wstr = std::wstring(pv.pwszVal); \ + } while (0) + + store_name(PKEY_Device_FriendlyName, pv, friendlyName); + store_name(PKEY_Device_DeviceDesc, pv, descriptionName); + store_name(PKEY_DeviceInterface_FriendlyName, pv, deviceName); + #undef store_name + log_wdebugcpp(L"Endpoint name: " + friendlyName); +} + +void Endpoint::activateEndpointSessions() { + if (this->flow != Flows::FLOW_PLAYBACK) { + log_debugcpp("recording. No seshes for u :("); + return; + } + + if (!sessionManager) { + if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), + CLSCTX_ALL, NULL, (void**) &sessionManager))) { + log_debugcpp("Couldn't open session manager2, huh"); + return; + } + } + + IAudioSessionEnumerator* sessionEnumerator = nullptr; + if (FAILED(sessionManager->GetSessionEnumerator(&sessionEnumerator))) { log_wdebugcpp(L"sesEnumeratorBros..."); exit(-5); return; } + + endpointSessions.resize(1, nullptr); + int sessionCount; + sessionEnumerator->GetCount(&sessionCount); + for (int i = 0; i < sessionCount; i++) { + IAudioSessionControl* sessionControlTmp; + if (FAILED(sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp))) { + exit(-6); + } + IAudioSessionControl2* sessionControl; + if(FAILED(sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl))){ + exit(-7); + } + sessionControlTmp->Release(); + Session* session = new Session(this, sessionControl, (size_t)i); + if (sessionControl->IsSystemSoundsSession() == S_OK) endpointSessions[0] = session; + else endpointSessions.push_back(session); + } + sessionEnumerator->Release(); +} + +/* + * void Endpoint::deleteSessionManager() { + * sessionManager->Release(); + * sessionManager = nullptr; + * } + */ + +void Endpoint::addSession(Session* session) { + session->setIndex(this->getSessionCount()); + endpointSessions.push_back(session); +} + +void Endpoint::activateEndpointVolume() { + //If this EP is created after init, COM won't be initialized on the executing thread. + HRESULT result = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + if (this->endpointVolume == nullptr) { + if(FAILED(endpoint->Activate( + IID_IAudioEndpointVolume, + CLSCTX_ALL, + NULL, + (void**)&this->endpointVolume))) { + log_debugcpp("No volume, huh"); + } + + //todo: check header + if(FAILED(endpoint->Activate(__uuidof(IAudioMeterInformation), + CLSCTX_ALL, NULL, (void**)&endpointPeakMeter))) { + log_debugcpp("peakbros..."); + } + } + 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::getPeakVolume() { + float peakVol; + if(endpointPeakMeter) endpointPeakMeter->GetPeakValue(&peakVol); + else return 0; + return peakVol; +} + +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 Endpoint::setState(EndpointState state){ + this->endpointState = state; + if(state == EndpointState::ENDPOINT_ACTIVE) { + this->activateEndpointVolume(); + this->reloadEndpointChannels(); + } +} -void Overseer::initCOMLibrary(){ - if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { log_debugcpp("si"); }; +EndpointState 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))) { + log_wdebugcpp(L"Master volume failed for endpoint: " + friendlyName); + }; + } else { + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { + log_wdebugcpp(L"Channel " + std::to_wstring(channel) + L" volume failed for endpoint: " + friendlyName); + }; + } +} + +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){ + if (!policyConfig) return; + + bool allRoles = false; + ERole val; + switch(role) { + case Roles::ROLE_CONSOLE: + val = eConsole; + break; + case Roles::ROLE_MULTIMEDIA: + val = eMultimedia; + break; + case Roles::ROLE_COMMUNICATIONS: + val = eCommunications; + break; + default: + allRoles = true; + break; + } + if (allRoles) { + policyConfig->SetDefaultEndpoint(endpointId.c_str(), eMultimedia); + //policyConfig->SetDefaultEndpoint(endpointId.c_str(), eConsole); + policyConfig->SetDefaultEndpoint(endpointId.c_str(), eCommunications); + } else policyConfig->SetDefaultEndpoint(endpointId.c_str(), val); +} + +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; + if(FAILED(this->endpoint->QueryInterface(__uuidof(IMMEndpoint), (void**)&flowGetter))) + { log_debugcpp("no flow..."); } + EDataFlow MSflow; + flowGetter->GetDataFlow(&MSflow); + this->flow = (MSflow == EDataFlow::eRender ? Flows::FLOW_PLAYBACK : Flows::FLOW_CAPTURE); + log_debugcpp("Endpoint flow: " + std::to_string(flow)); + flowGetter->Release(); +} + +Flows Endpoint::getFlow() { + return this->flow; +} + +/* sessions */ +std::vector Endpoint::getSessions() { + return endpointSessions; +} + +size_t Endpoint::getSessionCount() { + size_t sessionCount; + sessionCount = endpointSessions.size(); + return sessionCount; +} + +void Endpoint::registerNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->RegisterSessionNotification(ensc); +} + +void Endpoint::unregisterNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->UnregisterSessionNotification(ensc); +} + +void Endpoint::deleteSessions() { + for (auto session : endpointSessions) { + delete session; + } + endpointSessions.resize(0); +} + +Endpoint::~Endpoint(){ + //EPs are never deleted. + log_wdebugcpp(L"murio endpoint-san uwu"); + properties->Release(); + endpointVolume->Release(); + endpoint->Release(); + sessionManager->Release(); + for (auto session : endpointSessions) { + delete session; + } +} + +void Overseer::initCOMLibrary() { + if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { + log_debugcpp("Not even COM?"); }; //Retrieving endpoint enumerator @@ -42,60 +587,462 @@ void Overseer::initCOMLibrary(){ if(FAILED(CoCreateInstance( __uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnumerator)) ) - { log_debugcpp("si"); }; + { log_debugcpp("No MMDeviceEnum. Weird."); }; + 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); + + HRESULT hre = CoCreateInstance(__uuidof(CPolicyConfigClient), + NULL, CLSCTX_ALL, + __uuidof(IPolicyConfig7), (LPVOID *)&policyConfig); + if (hre != S_OK) exit(-1); + + //TODO: Release lpguid? + //TODO: Uninitialize COM } -void Overseer::reloadEndpoints() { +void Overseer::createEndpoints(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 collection + */ + 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, policyConfig, 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); + if (!temp) continue; + + 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) { + //This method is only called from the new endpoint callback and its subsequent thread, + //so another STA can be safely instantiated + if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { + log_debugcpp("EP Callback Thread failed. Sad!"); + return nullptr; + } + IMMDevice* newep; + if(FAILED(deviceEnumerator->GetDevice((LPCWSTR)endpointId.c_str(), &newep))) + log_debugcpp("ay caramba con la hot metida. Sad!"); + + Endpoint *endpoint = new Endpoint(newep, policyConfig); + + 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; + CoUninitialize(); + return endpoint; +} + +void Overseer::reportFinishedStateChange() { + epsc.reportFinishedStateChange(); +} + +Overseer::Overseer() : epsc(this) { + log_debugcpp("Initializing Overseer"); + + //Storing exe path for later use (mainly "Run on startup") + log_debugcpp("-Caching exe path"); + uint32_t cPathLen = 0; + wchar_t* cPath = getExeAbsPath(&cPathLen); + exeAbsPath = cPath; + + //Detecting install scope + wchar_t *machine = wcsstr(cPath, L"Program Files"); + if (!machine) + Environment::scope = HKEY_CURRENT_USER; + else Environment::scope = HKEY_LOCAL_MACHINE; + free(cPath); + //Initializing COM library + log_debugcpp("-Initializing COM"); initCOMLibrary(); - //Obtaining playback endpoint collection on this point in time - reloadEndpoints(); + //Obtaining playback endpoint collection + createEndpoints(Flows::FLOW_PLAYBACK); + //reloadEndpoints(Flows::FLOW_CAPTURE); } -//Overseer::int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint){ -//if (FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &endpointPtr))) -// return 1; -//return 0; -//} +NGuid Overseer::getGuid() { + return guid; +} -//int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); +void Overseer::registerEndpointSituationCallback() { + if(FAILED(deviceEnumerator->RegisterEndpointNotificationCallback(((IMMNotificationClient*)&epsc)))) { log_debugcpp("when no enchufas......"); } +} 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; +} + +void Overseer::updateEndpointInfo(std::wstring endpointId) { + //todo: reintroduce capture devices + playbackMutex.lock(); + log_wdebugcpp(L"new name Endpoint id: " + endpointId); + for(auto ep : playbackDevices) { + if (ep->getId() == endpointId && ep->getState() == EndpointState::ENDPOINT_ACTIVE) { + ep->updateName(); + osh->updateFrontEndpointName(ep); + break; + } + } + playbackMutex.unlock(); + epsc.reportFinishedStateChange(); +} + +Overseer::~Overseer(){ + //Overseer is never deleted. This is to annotate what would need to be taken care of. + log_debugcpp("jej"); + deviceEnumerator->Release(); + for(unsigned long long i = 0; i < playbackDevices.size(); i++){ + delete(playbackDevices.at(i)); + } +} + +wchar_t* Environment::getExeAbsPath(uint32_t *exeAbsPathLength) { + wchar_t *exeAbsPath = (wchar_t*)calloc(UNICODE_STRING_MAX_CHARS, sizeof(wchar_t)); + *exeAbsPathLength = GetModuleFileNameW( + NULL, + exeAbsPath, + UNICODE_STRING_MAX_CHARS + ); + return exeAbsPath; +} + +std::string Environment::createSettingsPath(SettingsTargetDirectory target) { + wchar_t* settingsPath = nullptr; + wchar_t settingsFile[] = L"\\settings.ini"; + uint32_t settingsFileLen = (sizeof(settingsFile) / sizeof(wchar_t)) - 1; + wchar_t maxPathBypass[] = L"\\\\?\\"; + uint32_t exePathLength = 0; + wchar_t folderPath[] = L"\\" LAPP_NAME; + uint32_t maxPathBypassLen = (sizeof(maxPathBypass)/ sizeof(wchar_t)) - 1; + uint32_t folderPathLen = (sizeof(folderPath) / sizeof(wchar_t)) - 1; + wchar_t* roamingPath = nullptr; + + log_wdebugcpp(L"Bypass size: " + std::to_wstring((sizeof(maxPathBypass)/sizeof(maxPathBypass[0])))); + + switch(target) { + case HOME_DIR: + { + if(SHGetKnownFolderPath( + FOLDERID_RoamingAppData, + 0, + NULL, + &roamingPath) + == S_OK) { + //Retrieve path len + uint32_t pathLen = 0; + wchar_t currentChar = roamingPath[pathLen]; + while(currentChar != '\0') { + pathLen++; + currentChar = roamingPath[pathLen]; + } + + settingsPath = (wchar_t*)calloc(pathLen + + maxPathBypassLen + + folderPathLen + + settingsFileLen, + sizeof(wchar_t)); + memcpy(settingsPath, maxPathBypass, sizeof(wchar_t) * maxPathBypassLen); + memcpy(settingsPath + (maxPathBypassLen), roamingPath, sizeof(wchar_t) * pathLen); + CoTaskMemFree(roamingPath); + memcpy(settingsPath + (maxPathBypassLen + pathLen), + folderPath, sizeof(wchar_t) * folderPathLen); + log_wdebugcpp(L"Settings folder path: " + std::wstring(settingsPath)); + + if(CreateDirectoryW(settingsPath, NULL) || GetLastError() == ERROR_ALREADY_EXISTS) { + memcpy(settingsPath + (maxPathBypassLen + pathLen + folderPathLen), + settingsFile, sizeof(wchar_t) * settingsFileLen); + std::string utf8path = utf16ToUtf8(settingsPath); + free(settingsPath); + return utf8path; + } + } + } + return nullptr; + break; + case APP_PATH: + { + //Executable dir + settingsPath = getExeAbsPath(&exePathLength); + + //reverse wcsstr + while(exePathLength >= 0) { + if(settingsPath[exePathLength] == '\\') { + memset(settingsPath + exePathLength, + 0, + (UNICODE_STRING_MAX_CHARS - exePathLength) * sizeof(wchar_t)); + break; + } else exePathLength--; + } + log_wdebugcpp(L"Exe folder: " + std::wstring(settingsPath)); + if((UNICODE_STRING_MAX_CHARS - exePathLength) > (settingsFileLen + 1)) { + memcpy(settingsPath + exePathLength, settingsFile, sizeof(wchar_t) * settingsFileLen); + std::string utf8path = utf16ToUtf8(settingsPath); + free(settingsPath); + return utf8path; + } + } + return nullptr; + break; + default: + return nullptr; + break; + } + return nullptr; +} + +void Environment::populateSystemValues() { + updateColors(); + Environment::startup = checkStartup(scope); +} + +void Environment::openControlPanel() { + 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"rundll32 shell32, Control_RunDLL mmsys.cpl"; + if(CreateProcessW( + NULL, + (wchar_t*)command.c_str(), + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + (LPSTARTUPINFOW)&startupConfig, + &processInfo + ) == true) { + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } +} + +ProcessedNativeEvent Environment::processTopLevelWindowMessage(void* msg) { +#ifdef WIN32 + MSG *message = static_cast(msg); + switch(message->message) { + case WM_SETTINGCHANGE: + //TODO: This looks like a future pain point. Ex handler would come in clutch + if(message->lParam && !wcscmp(((wchar_t*)message->lParam), L"ImmersiveColorSet")) + return updateColors(); + break; + default: + return ProcessedNativeEvent::NONE; + break; + } + return ProcessedNativeEvent::NONE; + //if (message->message != WM_SETTINGCHANGE) {return false;} +#endif +} + +ProcessedNativeEvent Environment::updateColors() { + // DwmGetColorizationColor( WM_DWMCOLORIZATIONCOLORCHANGED + DWORD value = 0; + DWORD size = sizeof(DWORD); + + LSTATUS result; + + //Theme bg color + result = RegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr, &value, &size); + lightMode = (bool)value; + + //Accent color + result = RegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\DWM", L"ColorizationColor", RRF_RT_REG_DWORD, nullptr, &value, &size); + if (result == ERROR_SUCCESS) { + accentColor = value; + } else accentColor = 0xffffffff; + + return ProcessedNativeEvent::COLORS; +} + +bool Environment::checkStartup(HKEY rootKeyFlags) { + //LSTATUS result; + DWORD typeReturned; + + //Checking if app entry exists + if(RegGetValueW(rootKeyFlags, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", LAPP_NAME, RRF_RT_REG_SZ, &typeReturned, nullptr, nullptr) != ERROR_SUCCESS && typeReturned != REG_SZ) + return false; + else return true; +} + +void Environment::updateStartupConfig(bool onStartup) { + wchar_t regSubKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run\\"; + + if(!onStartup) { + HKEY runKey; + if (RegOpenKeyExW( + scope, + regSubKey, + 0, + KEY_SET_VALUE, + &runKey + ) + == ERROR_SUCCESS) { + RegDeleteValueW(runKey, LAPP_NAME); + RegCloseKey(runKey); + } + } else { + LSTATUS result; + uint32_t cPathLen = 0; + wchar_t* cPath = getExeAbsPath(&cPathLen); + wchar_t* regPath = (wchar_t*)calloc(UNICODE_STRING_MAX_CHARS + 2, sizeof(wchar_t)); + //char* v = 0xFF'00'00'00'00'00'00'21; + regPath[0] = L'"'; + memcpy(regPath + 1, cPath, sizeof(wchar_t) * cPathLen); + memcpy(regPath + cPathLen + 1, L"\"", sizeof(wchar_t) * 2); + + result = RegSetKeyValueW( + scope, + regSubKey, + LAPP_NAME, + REG_SZ, + (void*)regPath, + (cPathLen + 3) * sizeof(wchar_t)); + /* + * if (result != ERROR_SUCCESS) { + * wchar_t* error; + * FormatMessageW( + * FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + * nullptr, + * (DWORD)result, + * LANG_USER_DEFAULT, + * error, + * 1, + * nullptr); + * LocalFree(error); + * } + */ + free(cPath); + } + return; +} + +void Environment::setStartupConfig(bool onStartup) { + //TODO: Use the cache!!!! lol + uint32_t cPathLen = 0; + wchar_t* cPath = getExeAbsPath(&cPathLen); + wchar_t startupParam[] = L"--change-startup"; + uint32_t startupParamLen = (sizeof(startupParam) / sizeof(wchar_t)) - 1; + wchar_t* completeParam = (wchar_t*)calloc(startupParamLen + 3, sizeof(wchar_t)); + memcpy(completeParam, startupParam, sizeof(wchar_t) * startupParamLen); + if (onStartup) + memcpy(completeParam + startupParamLen, L" 1", sizeof(wchar_t) * 3); + else + memcpy(completeParam + startupParamLen, L" 0", sizeof(wchar_t) * 3); + + if(scope == HKEY_LOCAL_MACHINE) { + ShellExecuteW( + NULL, + L"runas", + cPath, + completeParam, + NULL, // default dir + SW_SHOWNORMAL + ); + } else { + Environment::updateStartupConfig(onStartup); + } + free(cPath); + free(completeParam); + return; +} + +bool Environment::isLightMode() { + return lightMode; +} + +bool Environment::isToRunAtStartup() { + return startup; +} + +uint32_t Environment::getAccentColor() { + return accentColor; +} //int Overseer::getCaptureEndpoints(std::vector *captureEndpoints); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 90bc88b..5acedba 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,60 +1,240 @@ #pragma once -#define WIN32_LEAN_AND_MEAN +#include "msinclude.h" +#include "backsessionclasses.h" #include "global.h" -#include -#include +#include "contclasses.h" +//#include "environment.h" -#include -#include -#include -#include -#include +class EndpointVolumeCallback; +class Session; -#include -#include -#include -//#include -//#include -#include +// Convert a wide UTF16LE string to an UTF8 string +static inline std::string utf16ToUtf8(const wchar_t* wstr) { + if(!wstr || wstr[0] == '\0') return std::string(); + int size_needed = WideCharToMultiByte(CP_UTF8, + 0, + wstr, + -1, + NULL, + 0, + NULL, + NULL); + std::string str(size_needed, 0); + WideCharToMultiByte(CP_UTF8, + 0, + wstr, + -1, + &str[0], + size_needed, + NULL, + NULL); + return str; +} + +// Convert an UTF8 string to a wide UTF16LE String +/* + * std::wstring utf8_decode(const std::string &str) + * { + * if( str.empty() ) return std::wstring(); + * int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + * std::wstring wstrTo( size_needed, 0 ); + * MultiByteToWideChar (CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); + * return wstrTo; + * } + */ class Endpoint { public: - Endpoint(IMMDevice* endpoint); - void setVolume(float volume); - float getVolume(); - LPWSTR getName(); - //~Endpoint(); + Endpoint(IMMDevice* endpoint, IPolicyConfig7* policyConfig, uint64_t idx = 0); + //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(EndpointState state); + EndpointState getState(); + Roles getRoles(); + void setRoles(Roles role); + void assignRoles(Roles role); + void removeRoles(Roles role); + void setFlow(); + Flows getFlow(); + float getPeakVolume(); + std::wstring getId(); + std::wstring getName(); + void updateName(); + + 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); + void deleteSessions(); + void activateEndpointSessions(); + //void deleteSessionManager(); + std::mutex endpointSessionsMutex; + ~Endpoint(); private: - IMMDevice* endpoint; - IAudioEndpointVolume *endpointVolume ; + void inline activateEndpointVolume(); + + std::vector endpointSessions; + uint32_t channelCount = 0; + IMMDevice *endpoint; + IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; - LPWSTR friendlyName; - // LPWSTR endpointID = NULL; + IAudioMeterInformation *endpointPeakMeter = nullptr; + //IAudioClient *audioClient; + int64_t defTime, minTime; + IAudioSessionManager2 *sessionManager = nullptr; + Flows flow; + std::wstring friendlyName; + std::wstring descriptionName; + std::wstring deviceName; + std::wstring endpointId; + EndpointState endpointState; + Roles endpointRoles = (Roles)0; + uint64_t idx; + //Not implemented in llvm-mingw. Sad! todo: mingw patch + IPolicyConfig7* policyConfig; +}; + +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); + void updateVolumeInfo(AUDIO_VOLUME_NOTIFICATION_DATA newVolume, float* channelVolumes); + void reportFinished(); + //~EndpointVolumeCallback(); + + private: + ULONG ref = 1; + Endpoint* ep; + std::atomic wait = false; +}; + +class EndpointSituationCallback : public IMMNotificationClient { + public: + EndpointSituationCallback(Overseer* os); + 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 reportFinishedStateChange(); + private: + ULONG ref = 1; + Overseer* os; + std::atomic isEpStateChanging = false; }; class Overseer { - //TODO singleton? + public: Overseer(); + void registerEndpointSituationCallback(); + NGuid getGuid(); + std::vector getPlaybackEndpoints(); - void reloadEndpoints(); + std::vector getCaptureEndpoints(); + void updateEndpointInfo(std::wstring endpointId); + + void createEndpoints(Flows flow); + Endpoint* addEndpoint(std::wstring endpointId, /* out */ Flows* flow); + + void reportFinishedStateChange(); + + std::mutex playbackMutex; + std::mutex captureMutex; + //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; - IMMDeviceEnumerator *deviceEnumerator; - std::vector playbackDevices; void initCOMLibrary(); + + NGuid guid; + + IMMDeviceEnumerator *deviceEnumerator; + EndpointSituationCallback epsc; + + std::vector playbackDevices; + std::vector captureDevices; + IPolicyConfig7* policyConfig; + friend class Endpoint; //IMMDeviceCollection *deviceCollection; //int numCaptureEndpoints; //std::vector *captureDevices; }; +class EndpointNewSessionCallback : public IAudioSessionNotification { + private: + struct SessionThreadParams; + + public: + EndpointNewSessionCallback(EndpointHandler *eph); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnSessionCreated(IAudioSessionControl *NewSession); + void createSessionThread(SessionThreadParams params); + + private: + std::atomic wait = false; + ULONG ref = 1; + EndpointHandler *eph; + + struct SessionThreadParams { + EndpointHandler *eph; + Session *session; + bool isDelete; + }; +}; + +namespace Environment { + wchar_t* getExeAbsPath(uint32_t *exeAbsPathLength); + std::string createSettingsPath(SettingsTargetDirectory target); + void populateSystemValues(); + void openControlPanel(); + ProcessedNativeEvent processTopLevelWindowMessage(void* msg); + ProcessedNativeEvent updateColors(); + bool checkStartup(HKEY rootKeyFlags); + void updateStartupConfig(bool onStartup); + void setStartupConfig(bool onStartup); + bool isLightMode(); + bool isToRunAtStartup(); + uint32_t getAccentColor(); + + //todo: binary path cache unused + static std::wstring exeAbsPath; + static uint32_t exeAbsPathLen; + static bool lightMode; + static bool startup = false; + static HKEY scope; + static uint32_t accentColor; +}; diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp new file mode 100644 index 0000000..c9aaa85 --- /dev/null +++ b/src/back/backsessionclasses.cpp @@ -0,0 +1,425 @@ +#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) { + //TODO: Preguntar + while(sh->getVolumeInfo()->isNameChanged == true); + + sh->setName(std::wstring(NewDisplayName)); + sh->getVolumeInfo()->isNameChanged = true; + 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) { + if (DisconnectReason != DisconnectReasonDeviceRemoval) { + 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; + //https://matthewvaneerde.wordpress.com/2012/06/08/getting-audio-peak-meter-values-for-all-active-audio-sessions/ + if (FAILED(sessionControl->QueryInterface(__uuidof(IAudioMeterInformation), (void**)&meterInformation))) { log_wdebugcpp(L"sPeakbros......"); }; + //meterInformation = (IAudioMeterInformation*)sessionControl; + + 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"")) { + std::wstring exePath; + if (getExePath(pid, &exePath)) { + this->sessionName = exePath; + if (fetchName(exePath, pid)) goto nameFound; + } + if (fetchNameViaWindowName(pid, &this->sessionName)) goto nameFound; + } else { + this->sessionName = std::wstring(sessionDisplayName); + goto nameFound; + } + + nameFound: + 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; +} + +float Session::getPeakVolume() { + float peakVol; + if(meterInformation) meterInformation->GetPeakValue(&peakVol); + else return 0; + return peakVol; +} + +/* + * uint32_t Endpoint::getChannelCount(){ + * return (uint32_t)channelCount; + * } + */ + +std::wstring Session::getName() { + return sessionName; +} + +void Session::setName(std::wstring newName) { + this->sessionName = newName; +} + +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?")); }; +} + +bool Session::getExePath(DWORD pid, std::wstring *exePath) { + //std::wstring msixName; + HANDLE processHandle; + wchar_t fileName[UNICODE_STRING_MAX_CHARS]; + processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (processHandle != NULL) { + if (GetModuleFileNameEx(processHandle, NULL, fileName, UNICODE_STRING_MAX_CHARS) == 0) { + CloseHandle(processHandle); + return false; + } + } else { + log_wdebugcpp(L"aye no procname. -> " + std::to_wstring(GetLastError())); + return false; + } + + *exePath = std::wstring(fileName); + return true; +} + +bool Session::fetchNameViaFD(std::wstring exePath, DWORD pid, std::wstring *sessionName) { + /* File description retrieval: size and available lang-codepages */ + struct LANGANDCODEPAGE { + WORD wLanguage; + WORD wCodePage; + } *translationArray; + + DWORD filler; + DWORD fileVersionInfoSize = GetFileVersionInfoSizeExW + (FILE_VER_GET_LOCALISED | FILE_VER_GET_NEUTRAL, exePath.c_str(), &filler); + if (!fileVersionInfoSize) return false; + + void* fileVersionInfo = malloc(fileVersionInfoSize); + if(!GetFileVersionInfoExW(FILE_VER_GET_LOCALISED | FILE_VER_GET_NEUTRAL, + exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) { + return false; + } + + UINT translationArrayLen = 0; + if (!VerQueryValueW(fileVersionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&translationArray, &translationArrayLen)) { + free(fileVersionInfo); + return false; + } + //File descriptor parsing + //TODO: https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserpreferreduilanguages + /* It is possible to retrieve user languages and try to use one of those before falling back to whatever + * is available. Also possible to hardcode en-US or any other lang-codepage combo. When an actual translation + * sysem is put in place, I'll come finish this up. + */ + uint64_t availableLangs = (translationArrayLen / sizeof(LANGANDCODEPAGE)); + if (!availableLangs) { free(fileVersionInfo); return false; } + + int8_t syslangIdx = -1; + wchar_t metadataStringKey[256]; + wchar_t* metadataString = NULL; + for (UINT i = 0; i < availableLangs; i++) { + LANGID defaultUILanguage = GetUserDefaultUILanguage(); + if (defaultUILanguage != translationArray[i].wLanguage) + continue; + + syslangIdx = i; + break; + } + + UINT metadataStringSize = 0; + swprintf(metadataStringKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", + translationArray[(syslangIdx < 0 ? 0 : syslangIdx)].wLanguage, + translationArray[(syslangIdx < 0 ? 0 : syslangIdx)].wCodePage); + if (VerQueryValueW(fileVersionInfo, metadataStringKey, (LPVOID*)&metadataString, &metadataStringSize) + && metadataString[0] != '\0') { + free(fileVersionInfo); + *sessionName = std::wstring(metadataString); + return true; + } + swprintf(metadataStringKey, L"\\StringFileInfo\\%04x%04x\\ProductName", + translationArray[(syslangIdx < 0 ? 0 : syslangIdx)].wLanguage, + translationArray[(syslangIdx < 0 ? 0 : syslangIdx)].wCodePage); + if (VerQueryValueW(fileVersionInfo, metadataStringKey, (LPVOID*)&metadataString, &metadataStringSize) + && metadataString[0] != '\0') { + free(fileVersionInfo); + *sessionName = std::wstring(metadataString); + return true; + } + + if(fileVersionInfo) + free(fileVersionInfo); + + return false; +} + + +bool Session::fetchNameViaMSIX(std::wstring exePath, DWORD pid, std::wstring *sessionName) { + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if(!process) return false; + + //constant missing in mingw64. TB removed when I upgrade to a mingw64 ver that has it + #define APPLICATION_USER_MODEL_ID_MAX_LENGTH 130 + uint32_t length = APPLICATION_USER_MODEL_ID_MAX_LENGTH; + PWSTR userModelId = (PWSTR)malloc(length * sizeof(wchar_t)); + if(GetApplicationUserModelId(process, &length, userModelId) != ERROR_SUCCESS) { + CloseHandle(process); + return false; + } + CloseHandle(process); + + static constexpr wchar_t* prefix = L"shell:appsfolder\\"; + uint32_t prefixLen = wcslen(prefix); + uint32_t userModelIdLen = wcslen(userModelId); + wchar_t* fullName; + fullName = (prefixLen + userModelIdLen < length) + ? (wchar_t*)malloc(length * sizeof(wchar_t)) + : (wchar_t*)malloc((length * 2) * sizeof(wchar_t)); + for (int32_t i = prefixLen - 1; i >= 0; i--) { + fullName[i] = prefix[i]; + } + for (uint32_t i = 0; i < userModelIdLen + 1; i++) { + fullName[prefixLen + i] = userModelId[i]; + } + + IShellItem* si; + HRESULT hr = SHCreateItemFromParsingName(fullName, + nullptr, + IID_IShellItem, + (void**)&si + ); + free(fullName); + LPWSTR humanName = nullptr; + si->GetDisplayName(SIGDN_NORMALDISPLAY, &humanName); + if(humanName && humanName[0] != '\0') { + *sessionName = std::wstring(humanName); + CoTaskMemFree(humanName); + } + if(si) si->Release(); + + if (sessionName->length() > 0) + return true; + else return false; +} + + +bool Session::fetchNameViaWindowName(DWORD pid, std::wstring *sessionName) { + //lParam is documented as in, so... Beware of future explosions, ig? + std::pair params = { 0, pid }; + + BOOL result = EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL { + auto pParams = (std::pair*)(lParam); + + DWORD processId; + //IsWindowVisible(hwnd) &&&& GetWindow(hwnd, GW_OWNER) == 0 + if ( GetWindowThreadProcessId(hwnd, &processId) && processId == pParams->second) { + int length = GetWindowTextLength(hwnd); + if (!length) return TRUE; + + // Stop enumerating + SetLastError(-1); + pParams->first = hwnd; + return FALSE; + } + + // Continue enumerating + return TRUE; + } , (LPARAM)¶ms); + + if(!result && GetLastError() == -1 && params.first) { + //todo: double-dipping length... Not a fan + int length = GetWindowTextLength(params.first); + wchar_t* buffer = new wchar_t[length + 1]; + GetWindowTextW(params.first, buffer, length + 1); + *sessionName = buffer; + delete[] buffer; + return true; + } + return false; +} + +bool Session::fetchName(std::wstring exePath, DWORD pid) { + /* + * if(fetchNameViaWindowName(exePath, pid, &this->sessionName)) + * return; + * else if(fetchNameViaMSIX(exePath, pid, &this->sessionName)) + * return; + * else if(!fetchNameViaFD(exePath, pid, &this->sessionName)) + * this->sessionName = exePath; + */ + + if(fetchNameViaFD(exePath, pid, &this->sessionName)) + return true; + return fetchNameViaMSIX(exePath, pid, &this->sessionName); + //else if(!fetchNameViaWindowName(exePath, pid, &this->sessionName)) + /// this->sessionName = 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() { + meterInformation->Release(); + sessionControl->Release(); + sessionVolume->Release(); + meterInformation = nullptr; + sessionControl = nullptr; + sessionVolume = nullptr; +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h new file mode 100644 index 0000000..ae87f0a --- /dev/null +++ b/src/back/backsessionclasses.h @@ -0,0 +1,64 @@ +#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); + float getPeakVolume(); + void setMute(NGuid guid, bool muted); + bool getMute(); + SessionState getState(); + void setState(SessionState state); + void setIndex(size_t idx); + std::wstring getName(); + void setName(std::wstring newName); + void setStateCallback(SessionStateCallback *ssc); + void removeStateCallback(SessionStateCallback *ssc); + ~Session(); + //uint32_t getChannelCount(); + + private: + bool getExePath(DWORD pid, /*out*/ std::wstring *exePath); + bool fetchName(std::wstring exePath, DWORD pid); + bool fetchNameViaFD(std::wstring exePath, DWORD pid, /*out*/ std::wstring *sessionName); + bool fetchNameViaMSIX(std::wstring exePath, DWORD pid, /*out*/ std::wstring *sessionName); + bool fetchNameViaWindowName(DWORD pid, /*out*/ std::wstring *sessionName); + /* std::wstring fetchProcessName(DWORD pid); */ + std::wstring sessionName; + SessionState sessionState; + Endpoint* ep; + IAudioSessionControl2* sessionControl = nullptr; + IAudioMeterInformation* meterInformation = 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..7b474bb --- /dev/null +++ b/src/back/ipolicyconfig.h @@ -0,0 +1,311 @@ +#pragma once + +#ifndef __IPolicyConfig7_FWD_DEFINED__ +#define __IPolicyConfig7_FWD_DEFINED__ +typedef interface IPolicyConfig7 IPolicyConfig7; +#ifdef __cplusplus +interface IPolicyConfig7; +#endif +#endif /* __IPolicyConfig7_FWD_DEFINED__ */ + +#ifndef __CPolicyConfigClient_FWD_DEFINED__ +#define __CPolicyConfigClient_FWD_DEFINED__ +typedef class CPolicyConfigClient CPolicyConfigClient; +#endif /* __CPolicyConfigClient_FWD_DEFINED__ */ + +/***************************************************************************** + * CPolicyConfigClient coclass + */ + +DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf,0x0d, 0xe6,0x3d,0xf4,0x0c,0x2b,0xc9); +#ifdef __cplusplus +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf,0x0d, 0xe6,0x3d,0xf4,0x0c,0x2b,0xc9) +#endif +#endif + + +/***************************************************************************** + * IPolicyConfig7 interface + */ +#ifndef __IPolicyConfig7_INTERFACE_DEFINED__ +#define __IPolicyConfig7_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IPolicyConfig7, 0xf8679f50, 0x850a, 0x41cf, 0x9c,0x72, 0x43,0x0f,0x29,0x02,0x90,0xc8); +#if defined(__cplusplus) && !defined(CINTERFACE) +MIDL_INTERFACE("f8679f50-850a-41cf-9c72-430f290290c8") +IPolicyConfig7 : 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 + ); +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IPolicyConfig7, 0xf8679f50, 0x850a, 0x41cf, 0x9c,0x72, 0x43,0x0f,0x29,0x02,0x90,0xc8) +#endif + +#endif + + +#endif /* __IPolicyConfig7_INTERFACE_DEFINED__ */ + + + + +/* __CRT_UUID_DECL(IPolicyConfig10, 0xca286fc3, 0x91fd, 0x42c3, 0x8e,0x9b, 0xca,0xaf,0xa6,0x62,0x42,0xe3) */ +/* __CRT_UUID_DECL(CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf,0x0d, 0xe6,0x3d,0xf4,0x0c,0x2b,0xc9) */ + + +/* // ---------------------------------------------------------------------------- */ +/* // 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..52fccbd --- /dev/null +++ b/src/back/msinclude.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +//done by qt by def #define UNICODE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include + +#include "ipolicyconfig.h" +#include "audiometerinfo.h" + +// IAudioMeterInformation +/* GUID manual; */ +/* manual.Data1 = 0xc02216f6; */ +/* manual.Data2 = 0x8c67; */ +/* manual.Data3 = 0x4b5b; */ +/* manual.Data4[0] = 0x9d; */ +/* manual.Data4[1] = 0x00; */ +/* manual.Data4[2] = 0xd0; */ +/* manual.Data4[3] = 0x08; */ +/* manual.Data4[4] = 0xe7; */ +/* manual.Data4[5] = 0x3e; */ +/* manual.Data4[6] = 0x00; */ +/* manual.Data4[7] = 0x64; */ +//if(FAILED(endpoint->Activate((const _GUID) manual, + +//IMMEndpoint +/* 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; */ diff --git a/src/back/reimpl/audiometerinfo.h b/src/back/reimpl/audiometerinfo.h new file mode 100644 index 0000000..618ad34 --- /dev/null +++ b/src/back/reimpl/audiometerinfo.h @@ -0,0 +1,130 @@ +#ifndef __IAudioMeterInformation_FWD_DEFINED__ +#define __IAudioMeterInformation_FWD_DEFINED__ +typedef interface IAudioMeterInformation IAudioMeterInformation; + +#endif /* __IAudioMeterInformation_FWD_DEFINED__ */ + +#ifdef __cplusplus +extern "C"{ +#endif + +/* interface __MIDL_itf_endpointvolume_0000_0003 */ +/* [local] */ + +#ifndef __IAudioMeterInformation_INTERFACE_DEFINED__ +#define __IAudioMeterInformation_INTERFACE_DEFINED__ + +/* interface IAudioMeterInformation */ +/* [unique][helpstring][nonextensible][uuid][local][object] */ + +DEFINE_GUID(IID_IAudioMeterInformation, 0xc02216f6, 0x8c67, 0x4b5b, 0x9d,0x00, 0xd0,0x08,0xe7,0x3e,0x00,0x64); +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("c02216f6-8c67-4b5b-9d00-d008e73e0064") + IAudioMeterInformation : public IUnknown + { + public: + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetPeakValue( + /* [out] */ float *pfPeak) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetMeteringChannelCount( + /* [annotation][out] */ + _Out_ UINT *pnChannelCount) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetChannelsPeakValues( + /* [in] */ UINT32 u32ChannelCount, + /* [size_is][out] */ float *afPeakValues) = 0; + + virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE QueryHardwareSupport( + /* [annotation][out] */ + _Out_ DWORD *pdwHardwareSupportMask) = 0; + + }; + +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IAudioMeterInformation, 0xc02216f6, 0x8c67, 0x4b5b, 0x9d,0x00, 0xd0,0x08,0xe7,0x3e,0x00,0x64) +#endif +#else /* C style interface */ + + typedef struct IAudioMeterInformationVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IAudioMeterInformation * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IAudioMeterInformation * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IAudioMeterInformation * This); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetPeakValue )( + IAudioMeterInformation * This, + /* [out] */ float *pfPeak); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetMeteringChannelCount )( + IAudioMeterInformation * This, + /* [annotation][out] */ + _Out_ UINT *pnChannelCount); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *GetChannelsPeakValues )( + IAudioMeterInformation * This, + /* [in] */ UINT32 u32ChannelCount, + /* [size_is][out] */ float *afPeakValues); + + /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE *QueryHardwareSupport )( + IAudioMeterInformation * This, + /* [annotation][out] */ + _Out_ DWORD *pdwHardwareSupportMask); + + END_INTERFACE + } IAudioMeterInformationVtbl; + + interface IAudioMeterInformation + { + CONST_VTBL struct IAudioMeterInformationVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define IAudioMeterInformation_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define IAudioMeterInformation_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define IAudioMeterInformation_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define IAudioMeterInformation_GetPeakValue(This,pfPeak) \ + ( (This)->lpVtbl -> GetPeakValue(This,pfPeak) ) + +#define IAudioMeterInformation_GetMeteringChannelCount(This,pnChannelCount) \ + ( (This)->lpVtbl -> GetMeteringChannelCount(This,pnChannelCount) ) + +#define IAudioMeterInformation_GetChannelsPeakValues(This,u32ChannelCount,afPeakValues) \ + ( (This)->lpVtbl -> GetChannelsPeakValues(This,u32ChannelCount,afPeakValues) ) + +#define IAudioMeterInformation_QueryHardwareSupport(This,pdwHardwareSupportMask) \ + ( (This)->lpVtbl -> QueryHardwareSupport(This,pdwHardwareSupportMask) ) + +#endif /* COBJMACROS */ + +#endif /* C style interface */ + +#endif /* __IAudioMeterInformation_INTERFACE_DEFINED__ */ + +#ifdef __cplusplus +} +#endif + + + diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 30b2688..d2188f0 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,36 +1,464 @@ +#include "backlasses.h" #include "contclasses.h" -Overseer OverseerHandler::os; - -EndpointHandler::EndpointHandler(Endpoint *ept, QObject *parent) : QObject(parent) { - this->ept = ept; - eptName = QString::fromStdWString(ept->getName()); +void setConfigDirToDefaults() { + #define tryFileDir(dir, create) do { \ + OverseerHandler::settingsPath = Environment::createSettingsPath(dir); \ + set = ini::UserSettings::createSettings(OverseerHandler::settingsPath.c_str(), create); \ + if(set) { \ + return; \ + } else OverseerHandler::settingsPath.clear(); \ + } while(0) + #define tryOpenFileDir(dir) tryFileDir(dir, false) + #define tryCreateFileDir(dir) tryFileDir(dir, true) + + tryOpenFileDir(SettingsTargetDirectory::APP_PATH); + tryOpenFileDir(SettingsTargetDirectory::HOME_DIR); + tryCreateFileDir(SettingsTargetDirectory::HOME_DIR); + tryCreateFileDir(SettingsTargetDirectory::APP_PATH); + + return; + #undef tryOpenFileDir + #undef tryCreateFileDir + #undef tryFileDir } -void EndpointHandler::setValue(int value){ - ept->setVolume((float)value / 100); +EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { + this->idx = idx; + this->flow = flow; + this->ep = (flow == Flows::FLOW_PLAYBACK ? osh->getPlaybackEndpoints().at(idx) : osh->getCaptureEndpoints().at(idx)); + + this->callbackInfo.caller = osh->getGuid(); + //epName = ep->getName(); + this->setBackEndpointVolumeCallbackInfoContent(this->getState()); + osh->pushBackEndpointHandler(this, flow); } -QString EndpointHandler::getName(){ - return eptName; +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; } -float EndpointHandler::getVolume(){ - return ept->getVolume(); +void EndpointHandler::setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx){ + ephfv.visibility = state; + ephfv.frontIdx = frontIdx; } -Overseer OverseerHandler::getOverseer(){ - return os; +uint64_t EndpointHandler::getFrontVisibilityIndex(){ + return ephfv.frontIdx; } -OverseerHandler::OverseerHandler(QObject *parent) : QObject(parent) { +EndpointState EndpointHandler::getFrontVisibilityState(){ + return ephfv.visibility; +} + +Flows EndpointHandler::getFlow(){ + return ep->getFlow(); +} + +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(); + if (!epc) { + epc = new EndpointVolumeCallback(ep); + 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(EndpointState state){ + ep->setState(state); + this->setBackEndpointVolumeCallbackInfoContent(state); +} + +void EndpointHandler::setState(EndpointState state, uint64_t index){ + ep->setState(state); + this->setFrontVisibilityInfo((EndpointState)state, index); + this->setBackEndpointVolumeCallbackInfoContent(state); +} + +float EndpointHandler::getPeakVolume() { + return ep->getPeakVolume(); +} + +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) { + if(!ep->endpointSessionsMutex.try_lock()) return; + sessionHandlersMutex.lock(); + this->ep->addSession(session); + SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); + + sessionHandlers.push_back(sessionHandler); + ep->endpointSessionsMutex.unlock(); + sessionHandlersMutex.unlock(); + this->addSessionWidget(sessionHandler); } -std::vector* OverseerHandler::getEndpointHandlers(){ - return endpointHandlers; +void EndpointHandler::sendSessionToFront(SessionHandler* sh) { + this->addSessionWidget(sh); } -void OverseerHandler::setEndpointHandlers(std::vector *ephs){ - this->endpointHandlers = ephs; +void EndpointHandler::removeSessionFromFront(SessionHandler* sh) { + this->removeSessionWidget(sh); } + +void EndpointHandler::deleteSessions() { + ep->unregisterNewSessionNotification(ensc); + ensc->Release(); + ensc = nullptr; + for (auto sh : sessionHandlers) { + delete sh; + } + sessionHandlers.resize(0); + ep->deleteSessions(); + //ep->deleteSessionManager(); +} + +void EndpointHandler::createSessionHandlers() { + ep->activateEndpointSessions(); + 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); + } + } +} + +void EndpointHandler::createSessionHandlersCallback() { + ensc = new EndpointNewSessionCallback(this); + ep->registerNewSessionNotification(ensc); +} + +void EndpointHandler::lockSessionCollections() { + this->sessionHandlersMutex.lock(); + this->ep->endpointSessionsMutex.lock(); +} + +void EndpointHandler::unlockSessionCollections() { + this->sessionHandlersMutex.unlock(); + this->ep->endpointSessionsMutex.unlock(); +} + +void EndpointHandler::removeVolumeCallback() { + ep->removeVolumeCallback(epc); + epc->Release(); + epc = nullptr; +} + +EndpointHandler::~EndpointHandler() { + ep->removeVolumeCallback(epc); + ep->unregisterNewSessionNotification(ensc); + epc->Release(); + delete ep; +} + +OverseerHandler::OverseerHandler() { + this->os = new Overseer(); +} + +void OverseerHandler::setSettingsPath(std::string path) { + OverseerHandler::settingsPath = path; +} + +std::string OverseerHandler::getSettingsPath(){ + return OverseerHandler::settingsPath; +} + +void OverseerHandler::updateStartupConfig(bool onStartup) { + Environment::updateStartupConfig(onStartup); +} + +void OverseerHandler::setStartupConfig(bool onStartup) { + Environment::setStartupConfig(onStartup); +} + +void OverseerHandler::populateSystemValues() { + Environment::populateSystemValues(); +} + +void OverseerHandler::openControlPanel() { + Environment::openControlPanel(); +} + +ProcessedNativeEvent OverseerHandler::processTopLevelWindowMessage(void* msg) { + return Environment::processTopLevelWindowMessage(msg); +} + +bool OverseerHandler::isLightMode() { + return Environment::isLightMode(); +} + +bool OverseerHandler::isToRunAtStartup() { + return Environment::isToRunAtStartup(); +} + +uint32_t OverseerHandler::getAccentColor() { + return Environment::getAccentColor(); +} + +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::createEndpointHandlers(){ + //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)); + new EndpointHandler(i, Flows::FLOW_PLAYBACK); + 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)); + new EndpointHandler(i, Flows::FLOW_CAPTURE); + 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; + */ + } + os->registerEndpointSituationCallback(); +} + +EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId, /* out */ Flows *flow = nullptr) { + //This method is only called from the new endpoint callback and its subsequent thread + 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); + if (flow != nullptr) *flow = localFlow; + return newEph; +} + +void OverseerHandler::reportFinishedStateChange() { + os->reportFinishedStateChange(); +} + +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::roleBucketEntryCallback(Roles role, std::wstring endpointId){ + this->roleBucketEntry(role, endpointId); +} + +void OverseerHandler::setRoleBucketEntryFunction(std::function roleBucketEntry) { + this->roleBucketEntry = roleBucketEntry; +} + +void OverseerHandler::updateFrontEndpointName(Endpoint* ep) { + //todo: reintroduce capture devices + for (auto eph : playbackEndpointHandlers) { + if (eph->getEndpoint() == ep) eph->getCallbackInfo()->updateName = true; + } +} + +void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { + //TODO: Race condition!!!!! + std::vector allHandlers; + handlersPlaybackMutex.lock(); + handlersCaptureMutex.lock(); + os->playbackMutex.lock(); + os->captureMutex.lock(); + 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) goto end; + //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) goto end; + + if (eph && EndpointState::ENDPOINT_ACTIVE & state) { + eph->setState(EndpointState::ENDPOINT_ACTIVE); + this->addEndpointWidget(eph); + } else if (eph && eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE) { + eph->removeVolumeCallback(); + this->removeEndpointWidget(eph->getFrontVisibilityIndex()); + } + + end: + handlersPlaybackMutex.unlock(); + handlersCaptureMutex.unlock(); + os->playbackMutex.unlock(); + os->captureMutex.unlock(); + os->reportFinishedStateChange(); + 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; +} + +void OverseerHandler::lockEndpoints() { + os->playbackMutex.lock(); + os->captureMutex.lock(); +} + +void OverseerHandler::unlockEndpoints() { + os->playbackMutex.unlock(); + os->captureMutex.unlock(); +} + diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 647ca1d..cd0e03e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,49 +1,168 @@ #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 "settings.h" +#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; + bool updateName = false; }; +void setConfigDirToDefaults(); -class OverseerHandler : public QObject { - Q_OBJECT +class EndpointHandler { + +public: + EndpointHandler(uint64_t idx, Flows flow); + void setBackEndpointVolumeCallbackInfoContent(uint8_t state); + + //todo: replace all getEndpointHandler() + //todo: name refactor + BackEndpointVolumeCallbackInfo* getCallbackInfo(); + uint32_t getChannelCount(); + + void setIndex(uint64_t idx); + uint64_t getIndex(); + void setVolume(int channel, float volume); + + std::wstring getName(); + void setName(std::wstring newName); + 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(EndpointState state); + void setState(EndpointState state, uint64_t idx); + + float getPeakVolume(); + /* 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); + void deleteSessions(); + void createSessionHandlers(); + void createSessionHandlersCallback(); + std::mutex sessionHandlersMutex; + void lockSessionCollections(); + void unlockSessionCollections(); + void removeVolumeCallback(); + + ~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(); + static void setSettingsPath(std::string path); + static std::string getSettingsPath(); + static inline std::string settingsPath; + void updateStartupConfig(bool onStartup); + void setStartupConfig(bool onStartup); + void populateSystemValues(); + void openControlPanel(); + ProcessedNativeEvent processTopLevelWindowMessage(void* msg); + bool isLightMode(); + bool isToRunAtStartup(); + uint32_t getAccentColor(); + + //void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); + //void changeFrontDefaultsCallback(Roles role, std::wstring endpointId); + + void roleBucketEntryCallback(Roles role, std::wstring endpointId); + void setRoleBucketEntryFunction(std::function roleBucketEntry); + + void updateFrontEndpointName(Endpoint* ep); + //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 createEndpointHandlers(); + EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); + void reportFinishedStateChange(); + NGuid getGuid(); + + std::mutex handlersPlaybackMutex; + std::mutex handlersCaptureMutex; + void lockEndpoints(); + void unlockEndpoints(); 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; + std::function roleBucketEntry; + + /* 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..0f6caaf --- /dev/null +++ b/src/cont/contsessionclasses.cpp @@ -0,0 +1,91 @@ +#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(); +} + +void SessionHandler::setName(std::wstring newName){ + session->setName(newName); +} + +float SessionHandler::getPeakVolume(){ + return session->getPeakVolume(); +} + +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; + } +} + +SessionHandler::~SessionHandler() { + session->removeStateCallback(ssc); + ssc->Release(); +} diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h new file mode 100644 index 0000000..24c0ba7 --- /dev/null +++ b/src/cont/contsessionclasses.h @@ -0,0 +1,44 @@ +#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; + std::atomic isNameChanged = false; + //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(); + float getPeakVolume(); + void setName(std::wstring newName); + void reviseSessionShowing(SessionState state); + SessionVolumeInfo* getVolumeInfo(); + ~SessionHandler(); + 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..61855c1 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,86 @@ +#pragma once + +#if defined (QT_DEBUG) || defined (DEBUG) || defined (_DEBUG) + +#define PIPE_NAME "Mixerq-dev" + +#ifdef INIT_FILELOG + std::wstring_convert, wchar_t> converter; + FILE* fileLog; + errno_t lfResult; + bool writable = false; + + void inline initializeFileLogging() { + lfResult = fopen_s(&fileLog, "log.txt", "w"); + if (!lfResult) writable = true; + else writable = false; + } + +#else + extern std::wstring_convert, wchar_t> converter; + extern errno_t lfResult; + extern FILE* fileLog; + extern bool writable; + extern bool initializeFileLogging(); +#endif + +#define initialize_file_log() initializeFileLogging() + +template +std::bitset varToBitset(T info) { + std::bitset content(info); + return content; +} + +#define print_as_binary(info) varToBitset(info).to_string() + +#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 //_WIN32 + +#define log_to_file(fmt, cnt...) do { \ + if(writable) fprintf_s(fileLog, fmt,##cnt); \ +} while (0) + +#define close_file_log_buffer() do { \ + if(writable) { fflush(fileLog); fclose(fileLog); } \ +} while (0) + +#else +#define log_debugcpp(str) +#define log_wdebugcpp(str) +#define print_as_binary(info) +#define log_to_file(fmt, cnt...) +#define initialize_file_log() false +#define close_file_log_buffer() +#define PIPE_NAME "Mixerq" +#endif //DEBUG + +/* 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..8799892 100644 --- a/src/global.h +++ b/src/global.h @@ -1,4 +1,117 @@ #pragma once -#define log_debugcpp(str) do { \ - std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ - } while (0) + +#define __STDC_WANT_LIB_EXT1__ 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +//#include "settings.h" + +//TODO: Use tr();? QTranslator +#define APP_NAME "MixerQ" +#define LAPP_NAME L"MixerQ" + +#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" + +#define STRING_CP "Open Control Panel" +#define STRING_ABOUT "About" +#define STRING_STARTUP "Run at startup" +#define STRING_CHANNELS "Show endpoint channels" + +#define STRING_NOENDPOINT "No active endpoints" + +#define LSTRING_UNNAMED_SESSION L"Unnamed session" + +//INIT BACK + +enum SettingsTargetDirectory { + HOME_DIR = 0, + APP_PATH = (1 << 0), + CUSTOM = (1 << 1), +}; + +enum ProcessedNativeEvent { + NONE = 0, + COLORS = (1 << 0), +}; + +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); */ + /* } */ +}; +namespace ini { + class UserSettings; +} + +extern ini::UserSettings *set; +class OverseerHandler; +extern OverseerHandler *osh; + diff --git a/src/qt/meterslider.h b/src/qt/meterslider.h new file mode 100644 index 0000000..a59e16e --- /dev/null +++ b/src/qt/meterslider.h @@ -0,0 +1,19 @@ +#include + +class MeterSlider : public QSlider { + Q_OBJECT +private: + ~MeterSlider(); + float peakValue; + + friend class MixerStyle; +protected: + bool event(QEvent* ev) override; + void paintEvent(QPaintEvent *event) override; + +public: + //MeterSlider(Qt::Orientation orientation, QWidget *parent = nullptr); + //MeterSlider(QWidget* parent = nullptr) : MeterSlider(Qt::Vertical, parent){}; + void setPeakValue(float peakValue); + using QSlider::QSlider; +}; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 38ae3f2..a9148b9 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,53 +1,1401 @@ #include "qtclasses.h" +#include "meterslider.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); +#define POLLING_RATE 2 - setWindowTitle("slidea resbala nu c"); +bool DarkModeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) { + if (eventType == "windows_generic_MSG") { + ProcessedNativeEvent event = osh->processTopLevelWindowMessage(message); + switch(event) { + case COLORS: + StylingHelper::setBackgroundColor(osh->isLightMode()); - 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); - } + //Update accent color across entire app + { + uint32_t color = osh->getAccentColor(); + uint8_t r, g, b, a; + if (StylingHelper::argbToDiscreteValues(osh->getAccentColor(), &r, &g, &b, &a)) { + const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); + for (QWidget *widget : topLevelWidgets) { + if(qobject_cast(widget)) { + ((MainWindow*)widget)->updateColor(r, g, b, a); + } + } + } + } + return true; + break; + default: + break; + } + } + return false; } -void MainWindow::setEndpointHandlers(std::vector *ephs){ - this->ephs = ephs; +template +CustomWidgetEvent::CustomWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ + this->payload = payload; } /* - * 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); - * ... - */ + * MeterSlider::MeterSlider(Qt::Orientation orientation, QWidget* parent) { + * //style = new MixerStyle(); + * //this->setStyle(style); + * } + */ + + +MeterSlider::~MeterSlider() { + //delete style; +} + +void MeterSlider::setPeakValue(float peakValue) { + this->peakValue = peakValue; +} + +bool MeterSlider::event(QEvent* ev) { + if (ev->type() == QEvent::Wheel) { + ev->setAccepted(false); + return false; + } + return QSlider::event(ev); +} + +void MeterSlider::paintEvent(QPaintEvent *event) { + QStyleOptionSlider sliderComplex = QStyleOptionSlider(); + sliderComplex.initFrom(this); + + /* + * sliderComplex.initFrom(this); + * sliderComplex.subControls = QStyle::SC_None; + * sliderComplex.activeSubControls = QStyle::SC_None; + * sliderComplex.orientation = this->orientation(); + * sliderComplex.maximum = this->maximum(); + * sliderComplex.minimum = this->minimum(); + * sliderComplex.tickPosition = (QSlider::TickPosition)this->tickPosition(); + * sliderComplex.tickInterval = this->tickInterval(); + * sliderComplex.upsideDown = (this->orientation() == Qt::Horizontal) ? + * (this->invertedAppearance() != (sliderComplex.direction == Qt::RightToLeft)) + * : (!this->invertedAppearance()); + * sliderComplex.direction = Qt::LeftToRight; // we use the upsideDown option instead + * sliderComplex.sliderPosition = this->sliderPosition(); + * sliderComplex.sliderValue = this->value(); + * sliderComplex.singleStep = this->singleStep(); + * sliderComplex.pageStep = this->pageStep(); + * if (this->orientation() == Qt::Horizontal) + * sliderComplex.state |= QStyle::State_Horizontal; + * + * if (this->isSliderDown()) { + * sliderComplex.activeSubControls = QStyle::SC_SliderHandle; + * sliderComplex.state |= QStyle::State_Sunken; + * } else { + * sliderComplex.activeSubControls = QStyle::SC_SliderHandle; + * } + * + * //sliderComplex.subControls = QStyle::SC_SliderGroove; + * if (this->tickPosition() != NoTicks) sliderComplex.subControls |= QStyle::SC_SliderTickmarks; + * QStylePainter p(this); + */ + //p.drawComplexControl(QStyle::CC_Slider, sliderComplex); + + + + QStyleOptionSlider sliderComplex2 = QStyleOptionSlider(); + sliderComplex2.initFrom(this); + sliderComplex2.orientation = this->orientation(); + sliderComplex2.maximum = this->maximum(); + sliderComplex2.minimum = this->minimum(); + sliderComplex2.tickPosition = (QSlider::TickPosition)this->tickPosition(); + sliderComplex2.tickInterval = this->tickInterval(); + sliderComplex2.upsideDown = (this->orientation() == Qt::Horizontal) ? + (this->invertedAppearance() != (sliderComplex2.direction == Qt::RightToLeft)) + : (!this->invertedAppearance()); + sliderComplex2.subControls = QStyle::SC_SliderHandle; + sliderComplex2.direction = Qt::LeftToRight; // we use the upsideDown option instead + sliderComplex2.sliderPosition = this->sliderPosition(); + sliderComplex2.sliderValue = this->value(); + sliderComplex2.singleStep = this->singleStep(); + sliderComplex2.pageStep = this->pageStep(); + if (this->orientation() == Qt::Horizontal) + sliderComplex2.state |= QStyle::State_Horizontal; + + if (this->isSliderDown()) { + sliderComplex2.activeSubControls = QStyle::SC_SliderHandle; + sliderComplex2.state |= QStyle::State_Sunken; + } else { + sliderComplex2.activeSubControls = QStyle::SC_SliderHandle; + } + + sliderComplex2.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + if ((QSlider::TickPosition)this->tickPosition() != NoTicks) + sliderComplex2.subControls |= QStyle::SC_SliderTickmarks; + + QPainter painter(this); + QStyle* style = QApplication::style(); + style->drawComplexControl((QStyle::ComplexControl)CC_MeterSlider, &sliderComplex2, &painter, this); + + + //Q_D(QSlider); + /* + * QStylePainter p(this); + * QStyleOptionSlider opt; + * initStyleOption(&opt); + * + * opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + * //if (d->tickPosition != NoTicks) + * // opt.subControls |= QStyle::SC_SliderTickmarks; + * + * //p.drawComplexControl(QStyle::CC_Slider, opt); + */ + //QSlider::paintEvent(event); + + //IL CODE WENO lbockomet + /* + * int left = 0, top = 0, right = 0, bottom = 0; + * ((QWidget*)parent())->layout()->getContentsMargins(&left, &top, &right, &bottom); + * + * QStyle *style = QApplication::style(); + * //int lol = style->pixelMetric(QStyle::PM_SliderSpaceAvailable); + * QPainter painter(this); + * //painter.setPen(Qt::blue); + * painter.setOpacity(1.0); + * painter.setClipping(false); + * painter.setCompositionMode(QPainter::CompositionMode::CompositionMode_Source); + * float peakLength = (this->width() * (this->peakValue)); + * double stepWidth = (double)this->width() * ((double)this->singleStep() / (this->maximum() - this->minimum())); + * //Fusion seems to fuck around with bar's height and width + * //const qreal dpr = painter->device()->devicePixelRatio(); + * //QStyleOptionSlider sliderComplex = QStyleOptionSlider(); //slider.initFrom(this); + * QRect sliderSize = style->subControlRect(QStyle::CC_Slider, (QStyleOptionComplex*)&sliderComplex, QStyle::SC_SliderHandle); + * int handleCenterPos = QStyle::sliderPositionFromValue(this->minimum(), this->maximum(), this->value(), this->width()); + * int unattenuatedPeakMeter = ((this->width() * this->peakValue) >= handleCenterPos - (sliderSize.width() / 2)) + * ? this->width() - (sliderSize.width() / 2) + * : this->width() * this->peakValue; + * //QApplication::style()->subControlRect(QStyle::CC_Slider, (QStyleOptionComplex*)&slider, QStyle::SC_SliderHandle); + * painter.fillRect(0, (this->height() / 2) - 3, this->width(), + * 4, Qt::white); + * painter.fillRect(0, (this->height() / 2) - 3, this->width() * this->peakValue, + * 4, Qt::gray); + * painter.fillRect(0, (this->height() / 2) - 3, (this->width() - ((this->maximum() - this->value()) * stepWidth)) * this->peakValue, + * 4, Qt::green); + * // - ((this->maximum() - this->value()) * stepWidth)) + * //double ratio = ; + * double handleShift = (double)((sliderSize.width() * ((double)(this->maximum() - this->value()) / 100))); + * painter.fillRect((this->width() - ((this->maximum() - this->value()) * stepWidth)) - (sliderSize.width()) + handleShift, top / 2, sliderSize.width(), sliderSize.height() - bottom, Qt::magenta); + * //sliderComplex.subControls = QStyle::SC_SliderHandle; + * //p.drawComplexControl(QStyle::CC_Slider, sliderComplex); + */ +} + +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); +} + +void ExtendedCheckBox::paintEvent(QPaintEvent *event) { + QStylePainter p(this); + QStyleOptionButton opt; + initStyleOption(&opt); + opt.icon = this->icons; + p.drawControl((QStyle::ControlElement)CustomControlElement::CE_ExtendedCheckBox, opt); + //QStyle* style = QApplication::style(); + //style->drawComplexControl((QStyle::ComplexControl)CC_MeterSlider, &sliderComplex2, &painter, this); +} + +void ExtendedCheckBox::addIcon(char* const path, QIcon::State state) { + QString str(path); + QSvgRenderer rr(str); + QPixmap pixmap(64, 64); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + rr.render(&painter); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + uint8_t a, r, g, b; + if (StylingHelper::argbToDiscreteValues(osh->getAccentColor(), &r, &g, &b, &a)) { + QColor color(r, g, b, a); + painter.fillRect(pixmap.rect(), color); + } + painter.end(); + icons.addPixmap(pixmap, QIcon::Normal, state); + //this->setIcon(icons); + //icons.addFile(":/Icons/images/second.svg",QSize(32,32),QIcon::Normal,QIcon::Off); +} + + +QRect MainWindow::setSizePosition(QScreen* screen, int width, int height) { + //setGeometry ignores decoration size, theres others for that + QRect trayIconPos = this->trayIcon->geometry(); + int tix1, tix2, tiy1, tiy2; + trayIconPos.getCoords(&tix1, &tiy1, &tix2, &tiy2); + log_debugcpp("Tray Icon Pos: " + std::to_string(tix1) + " " + std::to_string(tix2)+" " + std::to_string(tiy1) + " " + std::to_string(tiy2)); + + this->setScreen(screen); + + QRect screenRes = screen->geometry(); + int srx1, srx2, sry1, sry2; + screenRes.getCoords(&srx1, &sry1, &srx2, &sry2); + log_debugcpp("Screen Res: " + std::to_string(srx1) + " " + std::to_string(srx2)+" " + std::to_string(sry1) + " " + std::to_string(sry2)); + + QRect availableRes = screen->availableGeometry(); + int arx1, arx2, ary1, ary2; + availableRes.getCoords(&arx1, &ary1, &arx2, &ary2); + log_debugcpp("Available res: " + std::to_string(arx1) + " " + std::to_string(arx2)+" " + std::to_string(ary1) + " " + std::to_string(ary2)); + + if(height > (ary2 - ary1)) height = (ary2 - ary1); + log_debugcpp("Height res: " + std::to_string(height)); + + uint8_t pos = 0; + int leftDistance = std::abs(srx1 - tix1); + int rightDistance = std::abs(srx2 - tix1); + int upDistance = std::abs(sry1 - tiy1); + int downDistance = std::abs(sry2 - tiy1); + + pos = (upDistance < downDistance) ? SpawnPos::UP : SpawnPos::DOWN; + pos = (leftDistance < rightDistance) ? pos | SpawnPos::LEFT : pos | SpawnPos::RIGHT; + + switch (pos) { + case SpawnPos::UP | SpawnPos::RIGHT: + this->addToolBar(Qt::BottomToolBarArea, mainMenuBar); + return QRect((arx2 - width + 1), ary1, width, height); + break; + case SpawnPos::DOWN | SpawnPos::LEFT: + this->addToolBar(Qt::TopToolBarArea, mainMenuBar); + return QRect(arx1, (ary2 - height + 1), width, height); + break; + case SpawnPos::DOWN | SpawnPos::RIGHT: + this->addToolBar(Qt::TopToolBarArea, mainMenuBar); + return QRect((arx2 - width + 1), (ary2 - height + 1), width, height); + break; + default: + this->addToolBar(Qt::BottomToolBarArea, mainMenuBar); + log_debugcpp("Failed positioning window"); + return QRect(500, 400, width, height); + break; + } +} + +void MainWindow::updateColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + QColor color(r, g, b, a); + QPalette pal = QPalette(color); + pal.setColor(QPalette::Highlight, color); + scrollArea->verticalScrollBar()->setPalette(pal); +} + +void MainWindow::compose(bool isVisible) { + //We need dynamically added child widgets to expand so that we know their height + this->setUpdatesEnabled(false); + uint64_t windowWidth; + uint64_t screenHeight; + uint64_t windowHeight; + /* + * Setting correct widget widths and heights + */ + log_to_file("[Compose]\n"); + screen = StylingHelper::getCurrentScreen(); + log_debugcpp("Screen: " + screen->model().toStdString() + " " + screen->name().toStdString()); + + QRect screenRes = screen->geometry(); + int srx1, srx2, sry1, sry2; + screenRes.getCoords(&srx1, &sry1, &srx2, &sry2); + log_debugcpp("(for Percentage) Screen Res: " + std::to_string(srx1) + " " + std::to_string(srx2)+" " + std::to_string(sry1) + " " + std::to_string(sry2)); + + windowWidth = (((uint64_t)std::abs(screenRes.width())) * widthRatio); + screenHeight = (uint64_t)std::abs(screenRes.height()); + log_debugcpp("Window Width: " + std::to_string(windowWidth)); + log_to_file("Window Width: %d \n", windowWidth); + this->scrollArea->setMaximumWidth((int)windowWidth); + this->scrollArea->setMinimumWidth((int)windowWidth); + this->mainMenuBar->setMinimumWidth((int)windowWidth); + this->mainMenuBar->setMaximumWidth((int)windowWidth); + + this->createLayout(new QGridLayout()); + for (auto *epw : ews) { + if (!epw) continue; + epw->updateChannelsVisibility(); + epw->calculateSize(windowWidth - scrollArea->verticalScrollBar()->sizeHint().width() + - widgetLayout->contentsMargins().left() + , screenHeight); + log_debugcpp("epw loop"); + log_debugcpp("epw roles: " + print_as_binary((epw->getEndpointHandler()->getRoles()))); + //std::bitset content = + //content); + //varToBitset(epw->getEndpointHandler()->getRoles()); + } + + /* + * Calculating window height + */ + windowHeight = 0; + int left = 0, top = 0, right = 0, bottom = 0; + for (int i = 0; i < 2; i++) { + if (!ews[i]) continue; + ews[i]->layout()->getContentsMargins(&left, &top, &right, &bottom); + windowHeight += ews[i]->sizeHint().height(); + windowHeight += top * 3; + log_debugcpp("windowHeight loop: " + std::to_string(windowHeight)); + } + windowHeight += mainMenuBar->sizeHint().height(); + log_debugcpp("windowHeight final value: " + std::to_string(windowHeight)); + + this->setUpdatesEnabled(true); + if (!isVisible) { + /* + * Undoing scrolling + */ + scrollArea->verticalScrollBar()->setValue(0); + /* + * Establishing initial window size and position + */ + setGeometry(setSizePosition(screen, windowWidth, windowHeight)); + } +} + +QScreen* MainWindow::getCurrentScreen() { + //note: Using cursor pos as screen detector. Flawed. + QPoint cursorPos = QCursor::pos(); + log_debugcpp("Cursor pos: " + std::to_string(cursorPos.ry()) + " " + std::to_string(cursorPos.rx())); + + for (QScreen *screen : QGuiApplication::screens()) { + QRect screenRect = screen->geometry(); + if (screenRect.contains(cursorPos)) { + return screen; + } + } + + return QGuiApplication::primaryScreen(); +} + +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; + + this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + widgetLayout = new QHBoxLayout(this); + //widgetLayout->setSizeConstraint(QLayout::SetFixedSize); + widgetLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + int left = 0, top = 0, right = 0, bottom = 0; + widgetLayout->getContentsMargins(&left, &top, &right, &bottom); + widgetLayout->setContentsMargins(0, top, 0, bottom); + + muteButton = new ExtendedCheckBox(this); + muteButton->addIcon(":/assets/mute.svg", QIcon::On); + muteButton->addIcon(":/assets/unmute.svg", QIcon::Off); + mainLabel = new QLabel(QString::fromStdWString(sh->getName()), this); + mainLabel->setToolTip(QString::fromStdWString(sh->getName())); + mainSlider = new MeterSlider(Qt::Horizontal, this); + + //mainLabel->setMaximumWidth(150 /*1/16ish 1080p*/); + //mainLabel->setMinimumWidth(150 /*1/16ish 1080p*/); + mainLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + mainSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + //mainSlider->setMinimumWidth(120 /*1/16 1080p*/); + 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); + //muteButton->setMaximumWidth(60 /*1/32th 1080p*/); + //muteButton->setMinimumWidth(60 /*1/32th 1080p*/); + 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 + widthSpacer = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); + widgetLayout->addItem(widthSpacer); + widgetLayout->addWidget(mainLabel, Qt::AlignLeft | Qt::AlignBottom); + widgetLayout->addWidget(muteButton, Qt::AlignRight | Qt::AlignBottom); + widgetLayout->addWidget(mainSlider, Qt::AlignRight | Qt::AlignBottom); + + //widgetLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + //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(this); + 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 + if (sh->getVolumeInfo()->isNameChanged) { + mainLabel->setText(QString::fromStdWString(sh->getName())); + mainLabel->setToolTip(QString::fromStdWString(sh->getName())); + sh->getVolumeInfo()->isNameChanged = false; + } + const float roundingFactor = 0.005; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((sh->getVolumeInfo()->mainVolume + roundingFactor) * 100)); + mainSlider->setPeakValue(sh->getPeakVolume()); + mainSlider->update(); + //log_wdebugcpp(L"Session: " + sh->getName() + L" peak value: " + std::to_wstring(sh->getPeakVolume())); + 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(POLLING_RATE); +} + +void SessionWidget::calculateSize(uint64_t width, uint64_t height) { + /* og 1080p 120% testing values */ + this->mainLabel->setMaximumWidth((int)(width * 0.30) /*1/16ish 1080p*/); + this->mainLabel->setMinimumWidth((int)(width * 0.30) /*1/16ish 1080p*/); + //this->mainLabel->setMaximumHeight((int)(height * 0.02)); + this->mainLabel->setMinimumHeight((int)(height * 0.02)); + this->muteButton->setMaximumWidth((int)(width * 0.10) /*1/32th 1080p*/); + this->muteButton->setMinimumWidth((int)(width * 0.10) /*1/32th 1080p*/); + //this->muteButton->setMinimumWidth((int)(width * 0.10) /*1/16 1080p*/); + this->mainSlider->setMinimumWidth((int)(width * 0.30) /*1/16 1080p*/); + widthSpacer->changeSize((int)(width * 0.20), 1, QSizePolicy::Expanding, QSizePolicy::Minimum /*200*/); + + log_to_file("\t[Session %s sizes]\n", converter.to_bytes(this->getName()).c_str()); + log_to_file("\tMain label Maximum size: %d, %d \n", mainLabel->maximumWidth(), mainLabel->maximumHeight()); + log_to_file("\tMain label Minimum size: %d, %d \n", mainLabel->minimumWidth(), mainLabel->minimumHeight()); + log_to_file("\tMute btn Maximum width: %d \n", muteButton->maximumWidth()); + log_to_file("\tMute btn Minimum width: %d \n", muteButton->minimumWidth()); + log_to_file("\tSlider Minimum width: %d \n", mainSlider->minimumWidth()); + log_to_file("\tSlider Maximum width: %d \n", mainSlider->maximumWidth()); + log_to_file("\tSpacer Minimum width: %d \n", widthSpacer->minimumSize().width()); + log_to_file("\tSpacer Maximum width: %d \n\n", widthSpacer->maximumSize().width()); +} + +std::wstring SessionWidget::getName() { + return sh->getName(); +} + +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(); + // volumePoller; +} + +ChannelWidget::ChannelWidget(uint32_t channelCount, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + this->eph = eph; + this->channelCount = channelCount; + this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + widgetLayout = new QGridLayout(this); + float volume = 100; + int left = 0, top = 0, right = 0, bottom = 0; + widgetLayout->getContentsMargins(&left, &top, &right, &bottom); + widgetLayout->setContentsMargins(0, top, 0, bottom); + + /* + * Channel sliders setup + */ + //uint32_t epChannelCount = eph->getChannelCount(); + for(uint64_t channel = 0, col = 0, row = 0; channel < channelCount && channelCount > 1; channel++){ + MeterSlider* tmp = new MeterSlider(Qt::Horizontal); + QLabel* tmpLb = new QLabel(""); + //tmp->setTickInterval(5); + tmp->setSingleStep(1); + tmp->setRange(0,100); + + volume = eph->getVolume(channel) * 100; + tmp->setValue((int) volume); + tmpLb->setText(QString::number(volume)); + //tmpLb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + //tmp->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + tmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + //mainSlider->setFocusPolicy(Qt::StrongFocus); + tmpLb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + this->channelSliders.push_back(tmp); + this->channelLabels.push_back(tmpLb); + widgetLayout->addWidget(tmp, row , col); + widgetLayout->addWidget(tmpLb, row + 1, col++); + if(channel % 2 != 0) { row += 2; col = 0; } + + //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, channel](int newValue){ + this->eph->setVolume(osh->getGuid(), channel, newValue); + this->channelLabels.at(channel)->setText(QString::number(newValue)); + }); + } + this->setLayout(widgetLayout); +} + +void ChannelWidget::updateChannel(int channel) { + this->channelSliders.at(channel)->blockSignals(true); + this->channelSliders.at(channel)->setValue((int)((eph->getCallbackInfo()->channelVolumes[channel] + roundingFactor) * 100)); + this->channelLabels.at(channel)->setText(QString::number((int)((eph->getCallbackInfo()->channelVolumes[channel] + roundingFactor) * 100))); + this->channelSliders.at(channel)->blockSignals(false); +} + +uint32_t ChannelWidget::getChannelCount() const { + return channelCount; +} + +EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t idx) : QWidget(parent) { + //todo: based on qgridlayout, name+mute should be its own widget, same with channels + row = 0; + this->idx = idx; + this->eph = eph; + + eph->createSessionHandlers(); + //todo: sussy + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); + this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + widgetLayout = new QGridLayout(this); + //this->setContentsMargins(0, 0, 0, 0); + //this->setLayout(widgetLayout); + log_debugcpp("epw main layout parent: " + std::to_string((intptr_t)(widgetLayout->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 MeterSlider(Qt::Horizontal, this); + mainVolumeLabel = new QLabel(this); + + if (this->eph->getState() != EndpointState::ENDPOINT_ACTIVE) { + widgetLayout->addWidget(mainLabel, row, 0); + //widgetLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); + return; + } + + //mainLabel->setMaximumWidth(350 /* 1080p 120%*/); + //mainLabel->setMinimumWidth(350 /* 1080p 120%*/); + mainLabel->setWordWrap(true); + mainLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + //mainLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + //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); + mainVolumeLabel->setText(QString::number(volume)); + log_debugcpp("ENDPOINT SET WITH VOLUME " + std::to_string(volume)); + + //mainMuteLayout = new QGridLayout(); + widgetLayout->addWidget(mainLabel, row, 0, 1, 2, Qt::AlignLeft | Qt::AlignVCenter); + widgetLayout->addWidget(muteButton, row, 2, Qt::AlignRight | Qt::AlignVCenter); + widgetLayout->addWidget(mainVolumeLabel, row, 3, Qt::AlignRight | Qt::AlignVCenter); + row++; + + widgetLayout->addWidget(mainSlider, row, 0, 1, 4, Qt::AlignVCenter); + //widgetLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + 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(); + if(epChannelCount > 1) { + cw = new ChannelWidget(epChannelCount, eph, nullptr); + cw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + widgetLayout->addWidget(cw, row++, 0, 1, 4 /*colmax*/, Qt::AlignTop); + cw->setVisible(false); + char* const channelSettings = set->getValue("show_channels"); + if(channelSettings && !(strcmp(channelSettings, "true"))){ + cw->setVisible(true); + } + } + + /* + * Role ExtendedCheckBoxes setup + */ + + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + uint8_t assignedRoles = eph->getRoles(); + uint8_t col = 0; + #define checkbox_setup(role, optor) do { \ + defaultRolesCheckBoxes.at(role)->setCheckState(assignedRoles optor role ? Qt::Checked : Qt::Unchecked); \ + defaultRolesCheckBoxes.at(role)->setDisabled(assignedRoles optor role ? true : false); \ + defaultRolesCheckBoxes.at(role)->setText(STRING_##role); \ + connect(defaultRolesCheckBoxes.at(role), &QCheckBox::stateChanged,[this] { \ + defaultRolesCheckBoxes.at(role)->setChecked(!(defaultRolesCheckBoxes.at(role)->isChecked())); \ + this->eph->setRoles(role); \ + }); \ + widgetLayout->addWidget(defaultRolesCheckBoxes.at(role), row, col++); \ + } while(0) + + checkbox_setup(ROLE_ALL, ==); + checkbox_setup(ROLE_CONSOLE, &); + checkbox_setup(ROLE_MULTIMEDIA, &); + checkbox_setup(ROLE_COMMUNICATIONS, &); + #undef checkbox_setup + 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; + if (eph->getCallbackInfo()->updateName) { + eph->getCallbackInfo()->updateName = false; + mainLabel->setText(QString::fromStdWString(eph->getName())); + mainLabel->setMinimumHeight(mainLabel->sizeHint().height()); + } + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((eph->getCallbackInfo()->mainVolume + roundingFactor) * 100)); + mainSlider->setPeakValue(eph->getPeakVolume()); + mainSlider->update(); + mainVolumeLabel->setText(QString::number(mainSlider->value())); + 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++){ + cw->updateChannel(i); + } + //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(POLLING_RATE); + + /* First Widget batch */ + for (size_t i = 0; i < eph->getSessionCount(); i++) { + SessionWidget* sessionWidget = new SessionWidget(i, eph->getSessionHandlers().at(i), this); + widgetLayout->addWidget(sessionWidget, row, 0, 1, 4 /* colmax */); + 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)); + }); + + log_debugcpp("ENDPOINT_WIDGETED"); + eph->createSessionHandlersCallback(); +} + +void EndpointWidget::addSessionWidget(CustomWidgetEvent* ev){ + this->setUpdatesEnabled(false); + uint64_t index = this->sessionWidgets.size(); + SessionWidget* sw = new SessionWidget(index, ev->payload, this); + ev->payload->setFrontIndex(index); + //MainWindow* mw = dynamic_cast(parent()); + //TODO: change mainwindow's widget name and subclass qwidget + const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); + for (QWidget *widget : topLevelWidgets) { + if (qobject_cast(widget)) { + QCoreApplication::instance()->postEvent + (widget, new QEvent((QEvent::Type)CustomQEvent::RecomposeMainWindow)); + } + } + this->widgetLayout->addWidget(sw, row, 0, 1, 4); + row++; + sessionWidgets.push_back(sw); + this->setUpdatesEnabled(true); + return; +} + +void EndpointWidget::removeSessionWidget(CustomWidgetEvent* ev){ + this->setUpdatesEnabled(false); + uint64_t i = ev->payload->getFrontIndex(); + SessionWidget* deceased = sessionWidgets.at(i); + deceased->setParent(nullptr); + deceased->hide(); + this->widgetLayout->removeWidget(deceased); + delete deceased; + sessionWidgets.at(i) = nullptr; + //row--; + ev->payload->setFrontIndex(INT_MAX); + this->setUpdatesEnabled(true); + //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); + this->eph->lockSessionCollections(); + this->eph->deleteSessions(); + this->eph->unlockSessionCollections(); + for(auto sw : sessionWidgets) { + delete sw; + } +} + +void MainWindow::customEvent(QEvent* ev) { + if (ev->type() == (QEvent::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); + } else if (ev->type() == (QEvent::Type)CustomQEvent::RecomposeMainWindow) { + ev->setAccepted(true); + if (this->isVisible()) this->compose(true); + } else if (ev->type() == (QEvent::Type)CustomQEvent::EndpointRoleChange) { + ev->setAccepted(true); + this->flushRoleChanges(); + } + QMainWindow::customEvent(ev); +} + +//__attribute__((optimize("O0", "unroll-loops"))) +void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev) { + uint64_t i = ev->payload; + this->ews.at(i)->setParent(nullptr); + this->widgetLayout->removeWidget(ews.at(i)); + //uint64_t saisu = ews.size(); + //delete ews.at(index); + delete ews.at(i); + ews.at(i) = nullptr; + //TODO: is a flattener really necessary? + //this->ewsUpdateTimer->start(); + return; +} + +void MainWindow::addEndpointWidget(CustomWidgetEvent* ev) { + EndpointWidget* epw = new EndpointWidget(ev->payload, containerWidget, this->ews.size()); + //epw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + epw->setParent(this); + if(this->widgetLayout) + this->widgetLayout->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::calculateSize(uint64_t width, uint64_t height) { + /* og 1080p 120% testing values */ + log_to_file("[EndpointWidget %s sizes]\n", converter.to_bytes(this->getEndpointHandler()->getName()).c_str()); + log_to_file("Params: {WWidth: %u SHeight: %u}\n", width, height); + //this->setMaximumWidth(width); + /* + * this->mainLabel->setMaximumWidth((int)(width * 0.50) /\* 1080p 120%*\/); + * this->mainLabel->setMinimumWidth((int)(width * 0.50) /\* 1080p 120%*\/); + */ + this->mainLabel->setMaximumSize((int)(width * 0.50), height /* 1080p 120%*/); + this->mainLabel->setMinimumSize((int)(width * 0.50), 1 /* 1080p 120%*/); + this->muteButton->setMaximumSize((int)(width * 0.10), height /* 1080p 120%*/); + this->mainVolumeLabel->setMaximumSize((int)(width * 0.10), height /* 1080p 120%*/); + for (auto roleCheckbox : defaultRolesCheckBoxes) { + roleCheckbox.second->setMaximumWidth((int)(width * 0.20) /* 1080p 120%*/); + log_to_file("Role %d width: %d \n", roleCheckbox.first, roleCheckbox.second->maximumWidth()); + } + log_to_file("Main label width: %d \n", this->mainLabel->maximumWidth()); + log_to_file("Mute button width: %d \n", this->muteButton->maximumWidth()); + log_to_file("Volume label width: %d \n", this->mainVolumeLabel->maximumWidth()); + + if (cw) { + this->cw->setMinimumSize(QSize(1, (height * 0.06) * (int)((cw->getChannelCount() / 2) + 0.5))); + //this->cw->setMaximumSize(QSize(width, (height * 0.06) * (int)((cw->getChannelCount() / 2) + 0.5))); + log_to_file("Channels Maximum size: %d, %d \n", cw->maximumWidth(), cw->maximumHeight()); + log_to_file("Channels Minimum size: %d, %d \n", cw->minimumWidth(), cw->minimumHeight()); + } + for (auto sw : sessionWidgets){ + if (sw) sw->calculateSize(width, height); + } +} + +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){ + mainVolumeLabel->setText(QString::number(newValue)); + this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); +} + +/* + * 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); + * } + */ + +void EndpointWidget::updateChannelsVisibility() { + if (!cw) return; + + char* const channelSettings = set->getValue("show_channels"); + if(channelSettings && !(strcmp(channelSettings, "true"))){ + cw->setVisible(true); + } else cw->setVisible(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; +} + +HeaderWidget::HeaderWidget(QWidget *parent) : QWidget(parent) { + widgetLayout = new QGridLayout(this); + + QString text; //= "&" STRING_ABOUT; + //about = new QPushButton(text, this); + #ifdef WIN32 + text = "&" STRING_CP; + openCP = new QPushButton(text, this); + connect(openCP, &QPushButton::clicked, [](){ osh->openControlPanel(); }); + + text = "&" STRING_CHANNELS; + channels = new QCheckBox(text, this); + char* const channelSettings = set->getValue("show_channels"); + if(channelSettings && !(strcmp(channelSettings, "true"))){ + channels->setChecked(true); + } + connect(channels, &QCheckBox::stateChanged, [this, parent](){ + //TODO: Find a better way to auto no-op when there's no settings file + if(set) { + set->setValue("show_channels", channels->isChecked(), sizeof("show_channels")); + if(!OverseerHandler::settingsPath.empty()){ + set->save(OverseerHandler::settingsPath.c_str()); + } + } + if(parent) { + QEvent explosion = QEvent((QEvent::Type)CustomQEvent::RecomposeMainWindow); + QCoreApplication::instance()->sendEvent + (parent, &explosion); + } + + }); + + text = "&" STRING_STARTUP; + startup = new QCheckBox(text, this); + if(osh->isToRunAtStartup()) { + startup->setChecked(true); + } + connect(startup, &QCheckBox::stateChanged, [this](){ + osh->setStartupConfig(startup->isChecked()); + }); + + widgetLayout->addWidget(openCP , 0, 0, 2, 2); + widgetLayout->addWidget(channels, 0, 2, 1, 2); + widgetLayout->addWidget(startup , 1, 2, 1, 2); + #endif + //widgetLayout->addWidget(about , 0, 2); + this->setLayout(widgetLayout); +} + +void MainWindow::createLayout(QGridLayout *newLayout) { + log_debugcpp("createLayout"); + widgetLayout->removeItem(lastRowSpacer); + QGridLayout *tempStore = this->widgetLayout; + this->widgetLayout = 0; + delete tempStore; + containerWidget->setLayout(newLayout); + this->widgetLayout = newLayout; + + bool areEndpoints = false; + uint64_t i = 0; + for (EndpointWidget *epw : ews) { + if (!epw) continue; + else areEndpoints = true; + log_wdebugcpp(L"epw name: " + epw->getEndpointHandler()->getName()); + //epw->setIndex(i); + this->widgetLayout->addWidget(epw, i++, 0); + } + if(areEndpoints) { + if (noEndpoints) { delete noEndpoints; noEndpoints = nullptr; } + widgetLayout->addItem(lastRowSpacer, i, 0); + } else { + if (!noEndpoints) noEndpoints = new QLabel(STRING_NOENDPOINT, this); + widgetLayout->addWidget(noEndpoints, i, 0); + } +} + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { + //setWindowState(Qt::WindowFullScreen); + //setCentralWidget(centralWidget); + //todo: ratio + setWindowFlags(Qt::Window | Qt::MSWindowsFixedSizeDialogHint); + setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); + //setAttribute(Qt::WA_TranslucentBackground); + //setStyleSheet("background: transparent; "); + //setStyleSheet("background-color: rgba(255,182,193);"); + setWindowTitle(STRING_TITLE); + /* + * Font setup + */ + int id = QFontDatabase::addApplicationFont(":/assets/selawk.ttf"); + QString family = QFontDatabase::applicationFontFamilies(id).at(0); + font = QFont(family); + font.setKerning(true); + font.setPointSize(12); + this->setFont(font); + //qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1"); + + /* + * Registering needed custom events + */ + //| Qt::FramelessWindowHint + QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); + QEvent::registerEventType(CustomQEvent::EndpointWidgetCreated); + QEvent::registerEventType(CustomQEvent::EndpointDefaultChange); + QEvent::registerEventType(CustomQEvent::SessionWidgetObsolete); + QEvent::registerEventType(CustomQEvent::SessionWidgetCreated); + QEvent::registerEventType(CustomQEvent::RecomposeMainWindow); + QEvent::registerEventType(CustomQEvent::EndpointRoleChange); + + /* This spacer provides proper spacing when window vertically > widgets. */ + lastRowSpacer = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + + ewsUpdateTimer = new QTimer(this); + recentlyClosedTimer = new QTimer(this); + containerWidget = new QWidget(); + //widget->setContentsMargins(0, 0, 0, 0); + widgetLayout = new QGridLayout(); + trayIcon = new QSystemTrayIcon(); + trayIconMenu = new QMenu(); + trayIconMenuQuit = new QAction(STRING_QUIT); + trayIconMenuOpenCP = new QAction(STRING_CP); + + ewsUpdateTimer->setSingleShot(true); + ewsUpdateTimer->setInterval(ewsUpdateTimerFrequency); + connect(ewsUpdateTimer, &QTimer::timeout, this, &MainWindow::reorderEndpointWidgetCollection); + + recentlyClosedTimer->setSingleShot(true); + recentlyClosedTimer->setInterval(recentlyClosedTimerFrequency); + connect(recentlyClosedTimer, &QTimer::timeout, this, [=]() { this->recentlyClosed = false; }); + //widget->setMinimumSize(QSize(300,300)); + //widget->setLayout(widgetLayout); + /* + * Scroll bar code + */ + scrollArea = new QScrollArea(this); + //widget->setAttribute(Qt::WA_TranslucentBackground); + scrollArea->setWidget(containerWidget); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setContentsMargins(QMargins(0, 0, 0, 0)); + + //ScrollBarFocusFilter focusFilter = new ScrollBarFocusFilter(this); + scrollArea->verticalScrollBar()->installEventFilter(this); + scrollArea->horizontalScrollBar()->installEventFilter(this); + //scrollArea->verticalScrollBar()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + //scrollArea->verticalScrollBar()->setSingleStep(1); + + setCentralWidget(scrollArea); + + /* + * Menu bar code + */ + mainMenuBar = new QToolBar(this); + hw = new HeaderWidget(this); + mainMenuBar->addWidget(hw); + mainMenuBar->setMovable(false); + this->addToolBar(Qt::BottomToolBarArea, mainMenuBar); + + /* + * Create initial endpoint widgets batch + */ + reloadEndpointWidgets(); + + /* + * Tray Icon code + */ + //trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayIconMenuOpenCP); + trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayIconMenuQuit); + connect(trayIconMenuOpenCP, &QAction::triggered, ([]() {osh->openControlPanel();}) ); + 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); + connect(qApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state){ + if(state == Qt::ApplicationState::ApplicationInactive) { + this->hide(); + //This recentlyClosed... kinda campy. + this->recentlyClosed = true; + this->recentlyClosedTimer->start(); + } + }); + + /* + * Set accent color + */ + uint8_t a, r, g, b; + if (StylingHelper::argbToDiscreteValues(osh->getAccentColor(), &r, &g, &b, &a)) + this->updateColor(r, g, b, a); + + /* + * Set of function callback definitons for EndpointSituationCallback + */ + osh->setRoleBucketEntryFunction([this](Roles role, std::wstring endpointId) { + std::pair entry = { role, endpointId }; + this->roleBucketList.push_back(entry); + QCoreApplication::instance()->postEvent(this, new QEvent((QEvent::Type)CustomQEvent::EndpointRoleChange),Qt::LowEventPriority); + }); + + 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)); + }); + +} + +bool MainWindow::eventFilter(QObject *object, QEvent *event) { + //QScrollBar* widgetCast = qobject_cast(object); + if (object == scrollArea->verticalScrollBar()) { + [[maybe_unused]] QEvent::Type tipo = event->type(); + if (event->type() == QHoverEvent::HoverEnter) { + log_debugcpp("Hover event: "); + log_debugcpp("\tGlobal pos: " + + std::to_string(((QHoverEvent*)event)->globalPosition().x()) + + ", " + + std::to_string(((QHoverEvent*)event)->globalPosition().y())); + log_debugcpp("\tNewWid pos: " + + std::to_string(((QHoverEvent*)event)->position().x()) + + ", " + + std::to_string(((QHoverEvent*)event)->position().y())); + log_debugcpp("\tOldWid pos: " + + std::to_string(((QHoverEvent*)event)->oldPos().x()) + + ", " + + std::to_string(((QHoverEvent*)event)->oldPos().y())); + + } + if (event->type() == QScrollEvent::ScrollFinished) { + QHoverEvent* hoverEvent = new QHoverEvent(QEvent::HoverEnter, + scrollArea->verticalScrollBar()->mapFromGlobal(QCursor::pos()), + QCursor::pos(), + scrollArea->verticalScrollBar()->mapFromGlobal(QCursor::pos())); + log_debugcpp("ScrollFinished event: "); + log_debugcpp("\tGlobal pos: " + + std::to_string(QCursor::pos().x()) + + ", " + + std::to_string(QCursor::pos().y())); + log_debugcpp("\tWidget pos: " + + std::to_string(scrollArea->verticalScrollBar()->mapFromGlobal(QCursor::pos()).x()) + + ", " + + std::to_string(scrollArea->verticalScrollBar()->mapFromGlobal(QCursor::pos()).y())); + QCoreApplication::instance()->postEvent(scrollArea->verticalScrollBar(), hoverEvent); + /* + * QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetObsolete, sessionHandler)); + */ + //scrollArea->setFocus(Qt::MouseFocusReason); + return false; + }} + return false; +} + +void MainWindow::flushRoleChanges() { + std::pair change = roleBucketList.front(); + roleBucketList.pop_front(); + + EndpointWidget *newDef = nullptr, *oldDef = nullptr; + for (uint64_t i = 0; i < ews.size(); i++) { + if(newDef && oldDef) break; + auto epw = this->ews.at(i); + if (!epw) continue; + if (epw->getEndpointHandler()->getId() == change.second) { + newDef = epw; + ews.at(i) = nullptr; + continue; + } + if (epw->getEndpointHandler()->getRoles() & change.first) { + oldDef = epw; + ews.at(i) = nullptr; + continue; + } + } + + this->changeFrontDefaults(change.first, newDef, oldDef); +} + +void MainWindow::changeFrontDefaults(Roles role, EndpointWidget* newDef, EndpointWidget* oldDef) { + //Sigh... MS's naive (non)API didn't help, but... + //Since widgets are removed previously, they must be added back. + //This produces unneeded vector size increases. + //Also, there's no freaking way this must be this illegible. + //TODO: Rewrite this method. Seriously. You'll have to get to, someday. + if (newDef && !oldDef) { + newDef->getDefaultRolesWidgets().at(role)->blockSignals(true); + uint8_t newDefCurRolesReversed = ~(newDef->getEndpointHandler()->getRoles()); + if(newDefCurRolesReversed & role) { + newDef->getEndpointHandler()->assignRoles(role); + QCoreApplication::instance()->sendEvent(newDef->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + } + uint8_t newDefIdx = newDef->getIndex(); + uint8_t newDefRoles = newDef->getEndpointHandler()->getRoles(); + if (newDefRoles == Roles::ROLE_ALL) { + newDef->setIndex(0); + this->ews[0] = newDef; + if(newDefCurRolesReversed & role) { + newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(true); + QCoreApplication::instance()->sendEvent(newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(false); + } + } else if ((newDefRoles & Roles::ROLE_MULTIMEDIA) || + (newDefRoles & Roles::ROLE_CONSOLE)){ + newDef->setIndex(0); + this->ews[0] = newDef; + } else if (newDefRoles & Roles::ROLE_COMMUNICATIONS) { + newDef->setIndex(1); + this->ews[1] = newDef; + } + log_debugcpp("newDef new idx: " + std::to_string(newDef->getIndex())); + newDef->getDefaultRolesWidgets().at(role)->blockSignals(false); + } + else if (oldDef && newDef) { + newDef->getDefaultRolesWidgets().at(role)->blockSignals(true); + newDef->getEndpointHandler()->assignRoles(role); + QCoreApplication::instance()->sendEvent(newDef->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + uint8_t newDefIdx = newDef->getIndex(); + uint8_t newDefRoles = newDef->getEndpointHandler()->getRoles(); + if (newDefRoles == Roles::ROLE_ALL) { + newDef->setIndex(0); + this->ews[0] = newDef; + newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(true); + QCoreApplication::instance()->sendEvent(newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(false); + } else if ((newDefRoles & Roles::ROLE_MULTIMEDIA) || + (newDefRoles & Roles::ROLE_CONSOLE)){ + newDef->setIndex(0); + this->ews[0] = newDef; + } else if (newDefRoles & Roles::ROLE_COMMUNICATIONS) { + newDef->setIndex(1); + this->ews[1] = newDef; + } + log_debugcpp("newDef new idx: " + std::to_string(newDef->getIndex())); + newDef->getDefaultRolesWidgets().at(role)->blockSignals(false); + + oldDef->getDefaultRolesWidgets().at(role)->blockSignals(true); + uint8_t oldDefRoles = oldDef->getEndpointHandler()->getRoles(); + if (oldDefRoles == Roles::ROLE_ALL) { + oldDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(true); + QCoreApplication::instance()->sendEvent(oldDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + oldDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL)->blockSignals(false); + } + oldDef->getEndpointHandler()->removeRoles(role); + QCoreApplication::instance()->sendEvent(oldDef->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + oldDefRoles = oldDef->getEndpointHandler()->getRoles(); + //this->ews[oldDef->getIndex()] = nullptr; + if ((oldDefRoles & Roles::ROLE_MULTIMEDIA) && + (oldDefRoles & Roles::ROLE_CONSOLE)) { + this-> ews[0] = oldDef; + oldDef->setIndex(0); + } else if (oldDefRoles & Roles::ROLE_COMMUNICATIONS) { + this-> ews[1] = oldDef; + oldDef->setIndex(1); + } else { + if (newDefIdx > 1) { + this->ews[newDefIdx] = oldDef; + oldDef->setIndex(newDefIdx); + } else { + this->ews.push_back(oldDef); + oldDef->setIndex(this->ews.size() - 1); + } + } + log_debugcpp("oldDef new idx: " + std::to_string(oldDef->getIndex())); + oldDef->getDefaultRolesWidgets().at(role)->blockSignals(false); + } + QCoreApplication::instance()->sendEvent + (this, new QEvent((QEvent::Type)CustomQEvent::RecomposeMainWindow)); +} + +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","primerita vez", QSystemTrayIcon::Information); + + hide(); + event->ignore(); + } +} + +void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + if (!this->isVisible() && !recentlyClosed) { + log_to_file("Recently Closed: %d \n", recentlyClosed); + this->compose(false); + this->showNormal(); + this->activateWindow(); + } + break; + default: + break; + } +} + +void MainWindow::reloadEndpointWidgets() { + size_t i = 0; + ews.resize(2); + + osh->handlersPlaybackMutex.lock(); + for (size_t epwIndex = 2; 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(osh->getPlaybackEndpointHandlers().at(i), containerWidget); + + log_wdebugcpp(L"epw name: " + epw->getEndpointHandler()->getName()); + if ((epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) || + (epw->getEndpointHandler()->getRoles() & Roles::ROLE_MULTIMEDIA)) + { ews[0] = epw; epw->setIndex(0); } + else if (epw->getEndpointHandler()->getRoles() & Roles::ROLE_COMMUNICATIONS) + { ews[1] = epw; epw->setIndex(1); } + else + { ews.push_back(epw); epw->setIndex(ews.size() - 1); } + } + } + osh->handlersPlaybackMutex.unlock(); + //todo: ya no está aquí tirao como tal, y de hecho, no ha fallado de la manera arriba descrita + //pero ahora lo ańado porque sí solo para garantizar que está y poder reusarlo luego lmao + widgetLayout->addItem(lastRowSpacer, i, 0); +} diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6ef82b0..1b2658b 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -1,34 +1,244 @@ #pragma once -#ifndef MAINWINDOW_H -#define MAINWINDOW_H -#include -#include -#include -#include -#include -#include -//#include "global.h" +#include "qtcommon.h" #include "contclasses.h" -//#include -//#include +#include "settings.h" +class MeterSlider; + +enum SpawnPos { + LEFT = (1 << 1), + RIGHT = (0 << 1), + UP = (1 << 0), + DOWN = (0 << 0) +}; + +enum CustomQEvent { + EndpointWidgetObsolete = 1001, + EndpointWidgetCreated = 1002, + EndpointDefaultChange = 1003, + SessionWidgetCreated = 1004, + SessionWidgetObsolete = 1005, + RecomposeMainWindow = 1006, + EndpointRoleChange = 1007 +}; + +class DarkModeEventFilter : public QAbstractNativeEventFilter { + +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override; +}; + +template +class CustomWidgetEvent : public QEvent { + +public: + CustomWidgetEvent(QEvent::Type type, T payload); + T payload; +}; +//Q_DECLARE_METATYPE(EndpointWidgetEvent) + +class ExtendedCheckBox : public QCheckBox { + Q_OBJECT +private: + QIcon icons; + +protected: + void customEvent(QEvent* ev) override; + void paintEvent(QPaintEvent* event) override; + +public: + //c++11: this inherits all parent's constructors unconditionally + using QCheckBox::QCheckBox; + void addIcon(char* const path, QIcon::State state); + //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(); + void calculateSize(uint64_t width, uint64_t height); + std::wstring getName(); + +public slots: + void updateMainVolume(int newValue); + void updateMute(int checked); + +private: + QLabel *mainLabel = nullptr; + MeterSlider *mainSlider = nullptr; + uint64_t idx; + QHBoxLayout *widgetLayout = nullptr; + ExtendedCheckBox *muteButton = nullptr; + SessionHandler* sh; + QTimer* volumePoller = nullptr; + QSpacerItem* widthSpacer; +}; + +class ChannelWidget : public QWidget { +Q_OBJECT + +public: + ChannelWidget(uint32_t channelCount, EndpointHandler* eph, QWidget *parent = nullptr); + //QSize minimumSizeHint() const override; + //void setMinimum(QSize minimum); + void updateChannel(int channel); + uint32_t getChannelCount() const; + +private: + const double roundingFactor = 0.005; + EndpointHandler* eph; + uint32_t channelCount; + std::vector channelSliders; + std::vector channelLabels; + QGridLayout *widgetLayout; + QSize minimum; +}; + +class EndpointWidget : public QWidget { +Q_OBJECT + +public: + EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr, uint64_t idx = INT_MAX); + //QSize minimumSizeHint() const override; + //void setMinimum(uint64_t height, double heightRatio); + void updateChannelsVisibility(); + EndpointHandler* getEndpointHandler(); + std::map getDefaultRolesWidgets(); + + void setIndex(uint64_t idx); + uint64_t getIndex(); + //void setVolume(int channel, float volume); + void calculateSize(uint64_t width, uint64_t height); + + ~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); + +//protected: + //bool event(QEvent* ev) override; + +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; + const int sessionCol = 2; + QCheckBox *muteButton = nullptr; + QLabel *mainLabel = nullptr; + QLabel *mainVolumeLabel = nullptr; + MeterSlider *mainSlider = nullptr; + std::vector channelSliders; + std::vector channelLabels; + QGridLayout *widgetLayout = nullptr; + QGridLayout *mainMuteLayout = nullptr; + std::map defaultRolesCheckBoxes; + + EndpointHandler* eph; + size_t defaultRolesVectorSize = 4; + QTimer* timer = nullptr; + uint64_t idx; + ChannelWidget* cw = nullptr; + std::vector sessionWidgets; + QSize minimum; + //std::vector *ephs; + //std::vector *sliders; + + //signals: + //void valueChanged(int value); + +}; + +class HeaderWidget : public QWidget { +Q_OBJECT + +public: + HeaderWidget(QWidget *parent = nullptr); + +private: + QGridLayout *widgetLayout; + //QPushButton *about; + #ifdef WIN32 + QPushButton *openCP; + QCheckBox *startup; + QCheckBox *channels; + #endif +}; 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(); + void compose(bool isVisible); + void updateColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +protected: + void closeEvent(QCloseEvent *event) override; + void customEvent(QEvent* ev) override; + QRect setSizePosition(QScreen* screen, int width, int height); + QScreen* getCurrentScreen(); + void createLayout(QGridLayout *newLayout); + +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; - QWidget *widget; - QGridLayout *layout; - //QLabel *pintas; + //std::vector *ephs; + bool eventFilter(QObject *object, QEvent *event) override; + void flushRoleChanges(); + void changeFrontDefaults(Roles role, EndpointWidget* newDef, EndpointWidget* oldDef); + + std::vector ews; + std::deque> roleBucketList; + std::mutex roleBucketMutex; + QWidget *containerWidget; + QGridLayout *widgetLayout; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; + QAction *trayIconMenuQuit; + QAction *trayIconMenuOpenCP; + QTimer *ewsUpdateTimer; + static constexpr uint64_t ewsUpdateTimerFrequency = 500; + double widthRatio = 0.28; + double dpr = 1.0; + bool recentlyClosed = false; + uint8_t recentlyClosedTimerFrequency = 1000; + QTimer *recentlyClosedTimer; + + QScrollArea *scrollArea; + HeaderWidget* hw; + QToolBar *mainMenuBar; + QScreen *screen; + QSpacerItem* lastRowSpacer; + QLabel* noEndpoints = nullptr; + + QFont font; + friend class EndpointWidget; //public slots: // void setEndpointHandlers(std::vector *ephs); @@ -36,5 +246,3 @@ private: //void valueChanged(int value); }; - -#endif diff --git a/src/qt/qtcommon.h b/src/qt/qtcommon.h new file mode 100644 index 0000000..61873eb --- /dev/null +++ b/src/qt/qtcommon.h @@ -0,0 +1,254 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +//#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +/* + * #else + * class QSlider; + * class QLabel; + * class QGridLayout; + * class QPushButton; + * class QWidget; + * class QMainWindow; + * #endif + */ + +#include "global.h" + +enum CustomComplexControl { + CC_MeterSlider = 0xf0000001 +}; + +enum CustomControlElement { + CE_ExtendedCheckBox = 0xf0000001 +}; + +namespace StylingHelper { + + static inline void setBackgroundColor(bool lightMode) { + //QApplication* app = (QApplication*)QApplication::instance(); + QPalette pal = QGuiApplication::palette(); + if(lightMode) { + pal.setColor(QPalette::Window, Qt::white); + pal.setColor(QPalette::WindowText, Qt::black); + } else { + pal.setColor(QPalette::Window, Qt::black); + pal.setColor(QPalette::WindowText, Qt::white); + } + QGuiApplication::setPalette(pal); + } + + static inline void setAccentColor(bool lightMode) { + //QApplication* app = (QApplication*)QApplication::instance(); + QPalette pal = QGuiApplication::palette(); + if(lightMode) { + pal.setColor(QPalette::Window, Qt::white); + pal.setColor(QPalette::WindowText, Qt::black); + } else { + pal.setColor(QPalette::Window, Qt::black); + pal.setColor(QPalette::WindowText, Qt::white); + } + QGuiApplication::setPalette(pal); + } + + static inline bool argbToDiscreteValues(uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a) { + if(!r || !g || !b || !a) return false; + + *a = (color >> 24) & 0xFF; + *r = (color >> 16) & 0xFF; + *g = (color >> 8) & 0xFF; + *b = color & 0xFF; + return true; + } + + static inline QLatin1String operator""_L1(const char* ch, uint64_t) { + return QLatin1String(ch); + } + + static inline qreal calculateDpi(const QStyleOption *option) { + #ifndef Q_OS_DARWIN + // Prioritize the application override, except for on macOS where + // we have historically not supported the AA_Use96Dpi flag. + if (QCoreApplication::testAttribute(Qt::AA_Use96Dpi)) + return 96; + #endif + + // Expect that QStyleOption::QFontMetrics::QFont has the correct DPI set + if (option) + return option->fontMetrics.fontDpi(); + + // Fall back to historical Qt behavior: hardcoded 72 DPI on mac, + // primary screen DPI on other platforms. + #ifdef Q_OS_DARWIN + return qstyleBaseDpi; + #else + return QGuiApplication::primaryScreen()->physicalDotsPerInch(); + #endif + } + + static inline qreal calculateDpi() { + return QFontMetrics(QApplication::font()).fontDpi(); + } + + /* + * #ifdef Q_OS_DARWIN + * static const qreal qstyleBaseDpi = 72; + * #else + * static const qreal qstyleBaseDpi = 96; + * #endif + */ + + //Q_GUI_EXPORT int qt_defaultDpiX() const; + + + static inline qreal dpiScaled(qreal value, qreal dpi) { + static const qreal qstyleBaseDpi = 96; + return value * dpi / qstyleBaseDpi; + } + + static inline qreal dpiScaled(qreal value, const QPaintDevice *device) { + return dpiScaled(value, device->logicalDpiX()); + } + + static inline qreal dpiScaled(qreal value, const QStyleOption *option) { + return dpiScaled(value, calculateDpi(option)); + } + + static inline bool isMacSystemPalette(const QPalette &pal) { + Q_UNUSED(pal); +#if defined(Q_OS_MACOS) + const QPalette *themePalette = QGuiApplicationPrivate::platformTheme()->palette(); + if (themePalette && themePalette->color(QPalette::Normal, QPalette::Highlight) == + pal.color(QPalette::Normal, QPalette::Highlight) && + themePalette->color(QPalette::Normal, QPalette::HighlightedText) == + pal.color(QPalette::Normal, QPalette::HighlightedText)) + return true; +#endif + return false; + } + + static inline QColor highlight(const QPalette &pal) { + if (isMacSystemPalette(pal)) + return QColor(60, 140, 230); + return pal.color(QPalette::Highlight); + } + + static inline QColor highlightedOutline(const QPalette &pal) { + QColor highlightedOutline = highlight(pal).darker(125); + if (highlightedOutline.value() > 160) + highlightedOutline.setHsl(highlightedOutline.hue(), highlightedOutline.saturation(), 160); + return highlightedOutline; + } + + static inline QColor getOutline(const QPalette &pal) { + if (pal.window().style() == Qt::TexturePattern) + return QColor(0, 0, 0, 160); + return pal.window().color().darker(140); + } + + static inline QColor getButtonColor(const QPalette &pal) { + QColor buttonColor = pal.button().color(); + int val = qGray(buttonColor.rgb()); + buttonColor = buttonColor.lighter(100 + qMax(1, (180 - val)/6)); + buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, buttonColor.value()); + return buttonColor; + } + + static inline QColor innerContrastLine() { + return QColor(255, 255, 255, 30); + } + + static inline QPixmap styleCachePixmap(const QSize &size, qreal pixelRatio) { + //not api + QPixmap cachePixmap = QPixmap(size * pixelRatio); + cachePixmap.setDevicePixelRatio(pixelRatio); + cachePixmap.fill(Qt::transparent); + return cachePixmap; + } + + static inline QColor backgroundColor(const QPalette &pal, const QWidget* widget) + { + #if QT_CONFIG(scrollarea) + if (qobject_cast(widget) && widget->parent() && + qobject_cast(widget->parent()->parent())) + return widget->parentWidget()->parentWidget()->palette().color(QPalette::Base); + #else + Q_UNUSED(widget); + #endif + return pal.color(QPalette::Base); + } + + static inline QPixmap svg2Pixmap(const QString& svgContent, + const QSize& size, + QPainter::CompositionMode mode = QPainter::CompositionMode_SourceOver) + { + QSvgRenderer rr(svgContent); + QImage image(size.width(), size.height(), QImage::Format_ARGB32); + QPainter painter(&image); + painter.setCompositionMode(mode); + image.fill(Qt::transparent); + rr.render(&painter); + return QPixmap::fromImage(image); + } + + static inline QScreen* getCurrentScreen() { + //note: Using cursor pos as screen detector. Flawed. + QPoint cursorPos = QCursor::pos(); + + for (QScreen *screen : QGuiApplication::screens()) { + QRect screenRect = screen->geometry(); + if (screenRect.contains(cursorPos)) { + return screen; + } + } + + return QGuiApplication::primaryScreen(); + } + +} + diff --git a/src/qt/qtvisuals.h b/src/qt/qtvisuals.h new file mode 100644 index 0000000..8902ed1 --- /dev/null +++ b/src/qt/qtvisuals.h @@ -0,0 +1,903 @@ +#pragma once + +#include "qtcommon.h" +#include "meterslider.h" + +using namespace StylingHelper; + +class MixerStyle : public QProxyStyle { + +public: + using QProxyStyle::QProxyStyle; + + //void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, + // QPainter *painter, const QWidget *widget) const override; + //Click on slider = move handle to click pos + int styleHint(QStyle::StyleHint hint, const QStyleOption* option = 0, const QWidget* widget = 0, + QStyleHintReturn* returnData = 0) const { + if (hint == QStyle::SH_Slider_AbsoluteSetButtons) + return (Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); + return baseStyle()->styleHint(hint, option, widget, returnData); + } + + QRect subControlRect(ComplexControl control, + const QStyleOptionComplex *option, + SubControl subControl, + const QWidget *widget) const { + QRect rect = QCommonStyle::subControlRect(CC_Slider, option, subControl, widget); + + switch (control) { +#if QT_CONFIG(slider) + case CC_MeterSlider: + if (const QStyleOptionSlider *slider = qstyleoption_cast(option)) { + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); + switch (subControl) { + case SC_SliderHandle: { + const bool bothTicks = (slider->tickPosition & QSlider::TicksBothSides) == QSlider::TicksBothSides; + if (slider->orientation == Qt::Horizontal) { + rect.setHeight(baseStyle()->pixelMetric(PM_SliderThickness, option, widget)); + rect.setWidth(baseStyle()->pixelMetric(PM_SliderLength, option, widget)); + int centerY = slider->rect.center().y() - rect.height() / 2; + if (!bothTicks) { + if (slider->tickPosition & QSlider::TicksAbove) + centerY += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerY -= tickSize - 1; + } + rect.moveTop(centerY); + } else { + rect.setWidth(baseStyle()->pixelMetric(PM_SliderThickness, option, widget)); + rect.setHeight(baseStyle()->pixelMetric(PM_SliderLength, option, widget)); + int centerX = slider->rect.center().x() - rect.width() / 2; + if (!bothTicks) { + if (slider->tickPosition & QSlider::TicksAbove) + centerX += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + centerX -= tickSize - 1; + } + rect.moveLeft(centerX); + } + } + break; + case SC_SliderGroove: { + QPoint grooveCenter = slider->rect.center(); + //rect.setWidth(std::abs(grooveCenter.x()) * 2); + const int grooveThickness = dpiScaled(7, option); //QStyleHelper::dpiScaled(7, option); + const bool bothTicks = (slider->tickPosition & QSlider::TicksBothSides) == QSlider::TicksBothSides; + if (slider->orientation == Qt::Horizontal) { + rect.setHeight(grooveThickness); + if (!bothTicks) { + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.ry() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.ry() -= tickSize - 1; + } + } else { + rect.setWidth(grooveThickness); + if (!bothTicks) { + if (slider->tickPosition & QSlider::TicksAbove) + grooveCenter.rx() += tickSize; + if (slider->tickPosition & QSlider::TicksBelow) + grooveCenter.rx() -= tickSize - 1; + } + } + rect.moveCenter(grooveCenter); + break; + } + default: + break; + } + } + return rect; + break; +#endif // QT_CONFIG(slider) +#if QT_CONFIG(scrollbar) + case CC_ScrollBar: + if (const QStyleOptionSlider *scrollbar = qstyleoption_cast(option)) { + QRect scrollBarRect = scrollbar->rect; + const uint64_t offset = 7; + scrollBarRect.setWidth(scrollBarRect.width() / 2); + scrollBarRect.setHeight(scrollBarRect.height() - 15); + int maxlen = ((scrollbar->orientation == Qt::Horizontal) ? + scrollBarRect.width() : scrollBarRect.height()); + int sliderlen; + + // calculate slider length + if (scrollbar->maximum != scrollbar->minimum) { + uint range = scrollbar->maximum - scrollbar->minimum; + sliderlen = (qint64(scrollbar->pageStep) * maxlen) / (range + scrollbar->pageStep); + + int slidermin = baseStyle()->pixelMetric(PM_ScrollBarSliderMin, scrollbar, widget); + if (sliderlen < slidermin || range > INT_MAX / 2) + sliderlen = slidermin; + if (sliderlen > maxlen) + sliderlen = maxlen; + } else { + sliderlen = maxlen; + } + + int sliderstart = sliderPositionFromValue(scrollbar->minimum, + scrollbar->maximum, + scrollbar->sliderPosition, + maxlen - sliderlen, + scrollbar->upsideDown); + + switch (subControl) { + /* + * case SC_ScrollBarSubLine: // top/left button + * if (scrollbar->orientation == Qt::Horizontal) { + * int buttonWidth = qMin(scrollBarRect.width() / 2, sbextent); + * rect.setRect(0, 0, buttonWidth, scrollBarRect.height()); + * } else { + * int buttonHeight = qMin(scrollBarRect.height() / 2, sbextent); + * rect.setRect(0, 0, scrollBarRect.width(), buttonHeight); + * } + * break; + * case SC_ScrollBarAddLine: // bottom/right button + * if (scrollbar->orientation == Qt::Horizontal) { + * int buttonWidth = qMin(scrollBarRect.width()/2, sbextent); + * rect.setRect(scrollBarRect.width() - buttonWidth, 0, buttonWidth, scrollBarRect.height()); + * } else { + * int buttonHeight = qMin(scrollBarRect.height()/2, sbextent); + * rect.setRect(0, scrollBarRect.height() - buttonHeight, scrollBarRect.width(), buttonHeight); + * } + * break; + */ + case SC_ScrollBarSubPage: // between top/left button and slider + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(0, 0, sliderstart, scrollBarRect.height()); + else + rect.setRect(0, 0, scrollBarRect.width(), sliderstart); + break; + case SC_ScrollBarAddPage: // between bottom/right button and slider + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderstart + sliderlen, 0, + maxlen - sliderstart - sliderlen, scrollBarRect.height()); + else + rect.setRect(0, sliderstart + sliderlen, scrollBarRect.width(), + maxlen - sliderstart - sliderlen); + break; + case SC_ScrollBarGroove: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(offset, 0, scrollBarRect.width(), + scrollBarRect.height()); + else + rect.setRect(0, offset, scrollBarRect.width(), + scrollBarRect.height() ); + break; + case SC_ScrollBarSlider: + if (scrollbar->orientation == Qt::Horizontal) + rect.setRect(sliderstart + offset, 0, sliderlen, scrollBarRect.height()); + else + rect.setRect(0, sliderstart + offset, scrollBarRect.width(), sliderlen); + break; + default: + break; + } + rect = visualRect(scrollbar->direction, scrollBarRect, rect); + return rect; + } + break; +#endif // QT_CONFIG(scrollbar) + default: + return baseStyle()->subControlRect(control, option, subControl, widget); + break; + } + + return QRect(); + + } + + void drawControl(ControlElement element, const QStyleOption *opt, + QPainter *p, const QWidget *widget) const + { + switch(element) { + case CE_ExtendedCheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast(opt)) { + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_CheckBoxIndicator, btn, widget); + //proxy()->drawPrimitive(PE_IndicatorCheckBox, &subopt, p, widget); + subopt.rect = subElementRect(SE_CheckBoxContents, btn, widget); + + //proxy()->drawControl(CE_CheckBoxLabel, &subopt, p, widget); + int alignment = visualAlignment(btn->direction, Qt::AlignLeft | Qt::AlignVCenter); + + if (!proxy()->styleHint(SH_UnderlineShortcut, btn, widget)) + alignment |= Qt::TextHideMnemonic; + QPixmap pix; + QRect textRect = btn->rect; + if (!btn->icon.isNull()) { + pix = btn->icon.pixmap(btn->iconSize, StylingHelper::getCurrentScreen()->devicePixelRatio(), + QIcon::Mode::Normal, btn->state & State_On ? QIcon::On : QIcon::Off); + proxy()->drawItemPixmap(p, btn->rect, alignment, pix); + if (btn->direction == Qt::RightToLeft) + textRect.setRight(textRect.right() - btn->iconSize.width() - 4); + else + textRect.setLeft(textRect.left() + btn->iconSize.width() + 4); + } + if (!btn->text.isEmpty()){ + proxy()->drawItemText(p, textRect, alignment | Qt::TextShowMnemonic, + btn->palette, btn->state & State_Enabled, btn->text, QPalette::WindowText); + } + // + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(SE_CheckBoxFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + default: + baseStyle()->drawControl(element, opt, p, widget); + break; + } + } + + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, + QPainter *painter, const QWidget *widget) const { + QColor outline; + switch (cc) { + #if QT_CONFIG(slider) + case CC_MeterSlider: + if (const QStyleOptionSlider *slider = qstyleoption_cast(option)) { + const qreal dpr = painter->device()->devicePixelRatio(); + const QColor buttonColor = getButtonColor(option->palette); + QRect groove = this->subControlRect(static_cast(CC_MeterSlider), option, SC_SliderGroove, widget); + QRect handle = this->subControlRect(static_cast(CC_MeterSlider), option, SC_SliderHandle, widget); + + bool horizontal = slider->orientation == Qt::Horizontal; + bool ticksAbove = slider->tickPosition & QSlider::TicksAbove; + bool ticksBelow = slider->tickPosition & QSlider::TicksBelow; + QColor activeHighlight = highlight(option->palette); + QPixmap cache; + QBrush oldBrush = painter->brush(); + QPen oldPen = painter->pen(); + QColor shadowAlpha(Qt::black); + shadowAlpha.setAlpha(10); + + if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) + outline = highlightedOutline(option->palette); + + if ((option->subControls & SC_SliderGroove) && groove.isValid()) { + QColor grooveColor; + grooveColor.setHsv(buttonColor.hue(), + qMin(255, (int)(buttonColor.saturation())), + qMin(255, (int)(buttonColor.value()*0.9))); + QString groovePixmapName = "slider_groove" + QString::number(groove.width());//QStyleHelper::uniqueName("slider_groove"_L1, option, groove.size(), dpr); + QRect pixmapRect(0, 0, groove.width(), groove.height()); + + // draw background groove + if (!QPixmapCache::find(groovePixmapName, &cache)) { + cache = styleCachePixmap(pixmapRect.size(), dpr); + QPainter groovePainter(&cache); + groovePainter.setRenderHint(QPainter::Antialiasing, true); + groovePainter.translate(0.5, 0.5); + QLinearGradient gradient; + if (horizontal) { + gradient.setStart(pixmapRect.center().x(), pixmapRect.top()); + gradient.setFinalStop(pixmapRect.center().x(), pixmapRect.bottom()); + } + else { + gradient.setStart(pixmapRect.left(), pixmapRect.center().y()); + gradient.setFinalStop(pixmapRect.right(), pixmapRect.center().y()); + } + groovePainter.setPen(QPen(outline)); + gradient.setColorAt(0, grooveColor.darker(110)); + gradient.setColorAt(1, grooveColor.lighter(110));//palette.button().color().darker(115)); + groovePainter.setBrush(gradient); + groovePainter.drawRoundedRect(pixmapRect.adjusted(1, 1, -2, -2), 1, 1); + groovePainter.end(); + QPixmapCache::insert(groovePixmapName, cache); + } + painter->drawPixmap(groove.topLeft(), cache); + + // draw groove lines (vol + unattenuated vol) + const MeterSlider* widgetCast = qobject_cast(widget); + if (widgetCast) { + QRect clipRect; + //if (!groovePixmapName.isEmpty()) groovePixmapName += QLatin1String("_unattenuated"); + //groovePixmapName += "_unattenuated"_L1; + //lf (!QPixmapCache::find(groovePixmapName, &cache)) { + cache = styleCachePixmap(pixmapRect.size(), dpr); + QPainter groovePainter(&cache); + QLinearGradient gradient; + if (horizontal) { + gradient.setStart(pixmapRect.center().x(), pixmapRect.top()); + gradient.setFinalStop(pixmapRect.center().x(), pixmapRect.bottom()); + } + else { + gradient.setStart(pixmapRect.left(), pixmapRect.center().y()); + gradient.setFinalStop(pixmapRect.right(), pixmapRect.center().y()); + } + QColor highlight = QColor(30,30,30,100); //this->highlight(option->palette); + QColor highlightedoutline = highlight.darker(140); + QColor grooveOutline = outline; + if (qGray(grooveOutline.rgb()) > qGray(highlightedoutline.rgb())) + grooveOutline = highlightedoutline; + + float peakValue = ((MeterSlider*)widget)->peakValue; + groovePainter.setRenderHint(QPainter::Antialiasing, true); + groovePainter.translate(0.5, 0.5); + groovePainter.setPen(QPen(grooveOutline)); + gradient.setColorAt(0, highlight); + gradient.setColorAt(1, highlight.lighter(130)); + groovePainter.setBrush(gradient); + groovePainter.drawRoundedRect(pixmapRect.adjusted( + 1, 1, + -pixmapRect.width() + (pixmapRect.width() * peakValue), + -2), 1, 1); + groovePainter.setPen(innerContrastLine()); + groovePainter.setBrush(Qt::green); + double stepWidth = (double)widgetCast->width() * ((double)widgetCast->singleStep() / (widgetCast->maximum() - widgetCast->minimum())); + groovePainter.drawRoundedRect(pixmapRect.adjusted( + 1, 1, + -pixmapRect.width() + ((widgetCast->width() - ((widgetCast->maximum() - widgetCast->value()) * stepWidth)) * peakValue), + -2), 1, 1); + + groovePainter.end(); + //QPixmapCache::insert(groovePixmapName, cache); + //} + //} + if (horizontal) { + if (slider->upsideDown) + clipRect = QRect(handle.right(), groove.top(), groove.right() - handle.right(), groove.height()); + else + clipRect = QRect(groove.left() + 2, groove.top() + 2, + groove.right() - 2, 2); + } else { + if (slider->upsideDown) + clipRect = QRect(groove.left(), handle.bottom(), groove.width(), groove.height() - (handle.bottom() - slider->rect.top())); + else + clipRect = QRect(groove.left(), groove.top(), groove.width(), handle.top() - groove.top()); + } + painter->save(); + painter->setClipRect(clipRect.adjusted(0, 0, 1, 1), Qt::IntersectClip); + painter->drawPixmap(groove.topLeft(), cache); + painter->restore(); + } + } + if (option->subControls & SC_SliderTickmarks) { + painter->save(); + painter->translate(slider->rect.x(), slider->rect.y()); + painter->setPen(outline); + int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget); + int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget); + int interval = slider->tickInterval; + if (interval <= 0) { + interval = slider->singleStep; + if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, + available) + - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + 0, available) < 3) + interval = slider->pageStep; + } + if (interval <= 0) + interval = 1; + + int v = slider->minimum; + int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + QVector lines; + while (v <= slider->maximum + 1) { + if (v == slider->maximum + 1 && interval == 1) + break; + const int v_ = qMin(v, slider->maximum); + int pos = sliderPositionFromValue(slider->minimum, slider->maximum, + v_, (horizontal + ? slider->rect.width() + : slider->rect.height()) - len, + slider->upsideDown) + len / 2; + int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0); + + if (horizontal) { + if (ticksAbove) { + lines += QLine(pos, slider->rect.top() + extra, + pos, slider->rect.top() + tickSize); + } + if (ticksBelow) { + lines += QLine(pos, slider->rect.bottom() - extra, + pos, slider->rect.bottom() - tickSize); + } + } else { + if (ticksAbove) { + lines += QLine(slider->rect.left() + extra, pos, + slider->rect.left() + tickSize, pos); + } + if (ticksBelow) { + lines += QLine(slider->rect.right() - extra, pos, + slider->rect.right() - tickSize, pos); + } + } + // in the case where maximum is max int + int nextInterval = v + interval; + if (nextInterval < v) + break; + v = nextInterval; + } + painter->drawLines(lines); + painter->restore(); + } + // draw handle + if ((option->subControls & SC_SliderHandle) ) { + QString handlePixmapName = "slider_handle" + QString::number(handle.height());//QStyleHelper::uniqueName("slider_handle"_L1, option,//handle.size(), dpr); + if (!QPixmapCache::find(handlePixmapName, &cache)) { + cache = styleCachePixmap(handle.size(), dpr); + QRect pixmapRect(0, 0, handle.width(), handle.height()); + QPainter handlePainter(&cache); + QRect gradRect = pixmapRect.adjusted(2, 2, -2, -2); + + // gradient fill + QRect r = pixmapRect.adjusted(1, 1, -2, -2); + QLinearGradient gradient = qt_fusion_gradient(gradRect, getButtonColor(option->palette),horizontal ? TopDown : FromLeft); + + handlePainter.setRenderHint(QPainter::Antialiasing, true); + handlePainter.translate(0.5, 0.5); + + handlePainter.setPen(Qt::NoPen); + handlePainter.setBrush(QColor(0, 0, 0, 40)); + handlePainter.drawRect(horizontal ? r.adjusted(-1, 2, 1, -2) : r.adjusted(2, -1, -2, 1)); + + handlePainter.setPen(QPen(getOutline(option->palette))); + if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) + handlePainter.setPen(QPen(highlightedOutline(option->palette))); + + handlePainter.setBrush(gradient); + handlePainter.drawRoundedRect(r, 2, 2); + handlePainter.setBrush(Qt::NoBrush); + handlePainter.setPen(innerContrastLine()); + handlePainter.drawRoundedRect(r.adjusted(1, 1, -1, -1), 2, 2); + + QColor cornerAlpha = outline.darker(120); + cornerAlpha.setAlpha(80); + + //handle shadow + handlePainter.setPen(shadowAlpha); + handlePainter.drawLine(QPoint(r.left() + 2, r.bottom() + 1), QPoint(r.right() - 2, r.bottom() + 1)); + handlePainter.drawLine(QPoint(r.right() + 1, r.bottom() - 3), QPoint(r.right() + 1, r.top() + 4)); + handlePainter.drawLine(QPoint(r.right() - 1, r.bottom()), QPoint(r.right() + 1, r.bottom() - 2)); + + handlePainter.end(); + QPixmapCache::insert(handlePixmapName, cache); + } + + painter->drawPixmap(handle.topLeft(), cache); + + } + painter->setBrush(oldBrush); + painter->setPen(oldPen); + } + break; + case CC_ScrollBar: + painter->save(); + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast(option)) { + bool wasActive = false; + qreal expandScale = 1.0; + qreal expandOffset = -1.0; + QObject *styleObject = option->styleObject; + + QColor buttonColor = calculateButtonColor(option->palette); + QColor gradientStartColor = buttonColor.lighter(104); + QColor gradientStopColor = buttonColor.darker(102); + + bool transient = styleHint(SH_ScrollBar_Transient, option, widget); + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool sunken = scrollBar->state & State_Sunken; + + QRect scrollBarSubLine = subControlRect(cc, scrollBar, SC_ScrollBarSubLine, widget); + QRect scrollBarAddLine = subControlRect(cc, scrollBar, SC_ScrollBarAddLine, widget); + QRect scrollBarSlider = subControlRect(cc, scrollBar, SC_ScrollBarSlider, widget); + QRect scrollBarGroove = subControlRect(cc, scrollBar, SC_ScrollBarGroove, widget); + + QRect rect = option->rect; + outline = highlightedOutline(option->palette); + QColor alphaOutline = outline; + alphaOutline.setAlpha(180); + + QColor arrowColor = option->palette.windowText().color(); + //QColor arrowColor = QColor(188,143,143,100); + arrowColor.setAlpha(160); + + const QColor appBgColor = QGuiApplication::palette().window().color(); + const bool isDarkBg = appBgColor.red() < 128 && appBgColor.green() < 128 && appBgColor.blue() < 128; + + if (transient) { + if (horizontal) { + rect.setY(rect.y() + 4.5 - expandOffset); + scrollBarSlider.setY(scrollBarSlider.y() + 4.5 - expandOffset); + scrollBarGroove.setY(scrollBarGroove.y() + 4.5 - expandOffset); + + rect.setHeight(rect.height() * expandScale); + scrollBarGroove.setHeight(scrollBarGroove.height() * expandScale); + } else { + rect.setX(rect.x() + 4.5 - expandOffset); + scrollBarSlider.setX(scrollBarSlider.x() + 4.5 - expandOffset); + scrollBarGroove.setX(scrollBarGroove.x() + 4.5 - expandOffset); + + rect.setWidth(rect.width() * expandScale); + scrollBarGroove.setWidth(scrollBarGroove.width() * expandScale); + } + } + + // Paint groove + if ((!transient || scrollBar->activeSubControls || wasActive) && scrollBar->subControls & SC_ScrollBarGroove) { + QLinearGradient gradient(rect.center().x(), rect.top(), + rect.center().x(), rect.bottom()); + if (!horizontal) + gradient = QLinearGradient(rect.left(), rect.center().y(), + rect.right(), rect.center().y()); + if (!transient || !isDarkBg) { + gradient.setColorAt(0, buttonColor.darker(107)); + gradient.setColorAt(0.1, buttonColor.darker(105)); + gradient.setColorAt(0.9, buttonColor.darker(105)); + gradient.setColorAt(1, buttonColor.darker(107)); + } else { + gradient.setColorAt(0, appBgColor.lighter(157)); + gradient.setColorAt(0.1, appBgColor.lighter(155)); + gradient.setColorAt(0.9, appBgColor.lighter(155)); + gradient.setColorAt(1, appBgColor.lighter(157)); + } + + painter->save(); + //painter->fillRect(rect, gradient); + painter->setPen(Qt::NoPen); + painter->setPen(alphaOutline); + + QColor subtleEdge = alphaOutline; + subtleEdge.setAlpha(40); + painter->setPen(subtleEdge); + //painter->setBrush(Qt::NoBrush); + painter->setOpacity(0.3); + painter->setBrush(Qt::gray); + painter->drawRoundedRect(scrollBarGroove, 5, 5); + //painter->drawRoundedRect(scrollBarGroove, 5, 5); + painter->restore(); + } + + QRect pixmapRect = scrollBarSlider; + QColor highlightColor = option->palette.highlight().color(); + /* + * QLinearGradient gradient(pixmapRect.center().x(), pixmapRect.top(), + * pixmapRect.center().x(), pixmapRect.bottom()); + * if (!horizontal) + * gradient = QLinearGradient(pixmapRect.left(), pixmapRect.center().y(), + * pixmapRect.right(), pixmapRect.center().y()); + */ + + //QLinearGradient highlightedGradient = gradient; + + QColor heldColor = 0xc4e3d7e0;//Qt::gray;//osh->isLightMode() ? Qt::white : Qt::black; + //gradient.setColorAt(0, option->palette.highlight().color()); + //gradient.setColorAt(1, option->palette.highlight().color()); + + //highlightedGradient.setColorAt(0, gradientStartColor.darker(102)); + //highlightedGradient.setColorAt(1, gradientStopColor.lighter(102)); + + // Paint slider + if (scrollBar->subControls & SC_ScrollBarSlider) { + log_debugcpp("Final scrollbar paint if"); + if (transient) { + QRect rect = scrollBarSlider.adjusted(horizontal ? 1 : 2, horizontal ? 2 : 1, -1, -1); + painter->setPen(Qt::NoPen); + painter->setBrush(isDarkBg ? lightShade() : darkShade()); + int r = qMin(rect.width(), rect.height()) / 2; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->drawRoundedRect(rect, r, r); + painter->restore(); + } else { + QRect pixmapRect = scrollBarSlider; + painter->setPen(QPen(alphaOutline)); + if (option->state & State_Sunken + && scrollBar->activeSubControls & SC_ScrollBarSlider) + painter->setBrush(heldColor); + else if (option->state & State_MouseOver + && scrollBar->activeSubControls & SC_ScrollBarSlider) { + painter->setBrush(highlightColor); + } + else //if (!isDarkBg) + + if(!isDarkBg) + painter->setBrush(Qt::black); + else painter->setBrush(Qt::white); + //else + // painter->setBrush(heldColor); + + painter->drawRoundedRect(pixmapRect.adjusted(horizontal ? -1 : 0, horizontal ? 0 : -1, horizontal ? 0 : -1, horizontal ? -1 : 0), 5, 5); + + painter->setPen(innerContrastLine()); + painter->drawRoundedRect(scrollBarSlider.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, -1, -1), 5, 5); + + // Outer shadow + // painter->setPen(subtleEdge); + // if (horizontal) { + //// painter->drawLine(scrollBarSlider.topLeft() + QPoint(-2, 0), scrollBarSlider.bottomLeft() + QPoint(2, 0)); + //// painter->drawLine(scrollBarSlider.topRight() + QPoint(-2, 0), scrollBarSlider.bottomRight() + QPoint(2, 0)); + // } else { + //// painter->drawLine(pixmapRect.topLeft() + QPoint(0, -2), pixmapRect.bottomLeft() + QPoint(0, -2)); + //// painter->drawLine(pixmapRect.topRight() + QPoint(0, 2), pixmapRect.bottomRight() + QPoint(0, 2)); + // } + } + } + + /* + * // The SubLine (up/left) buttons + * if (!transient && scrollBar->subControls & SC_ScrollBarSubLine) { + * if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) + * painter->setBrush(gradientStopColor); + * else if ((scrollBar->activeSubControls & SC_ScrollBarSubLine)) + * painter->setBrush(highlightedGradient); + * else + * painter->setBrush(gradient); + * + * painter->setPen(Qt::NoPen); + * painter->drawRoundedRect(scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0), 1, 1); + * painter->setPen(QPen(alphaOutline)); + * if (option->state & State_Horizontal) { + * painter->drawRect(scrollBarSubLine.adjusted(horizontal ? 0 : 1, 0, horizontal ? 1 : 0, horizontal ? -1 : 0)); + * } else { + * painter->drawRoundedRect(scrollBarSubLine.adjusted(0, 0, horizontal ? 0 : -1, 0), 1, 1); + * } + * + * QRect upRect = scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, horizontal ? -2 : -1, horizontal ? -1 : -2); + * painter->setBrush(Qt::NoBrush); + * painter->setPen(innerContrastLine()); + * painter->drawRect(upRect); + * + * // Arrows + * Qt::ArrowType arrowType = Qt::UpArrow; + * if (option->state & State_Horizontal) + * arrowType = option->direction == Qt::LeftToRight ? Qt::LeftArrow : Qt::RightArrow; + * qt_fusion_draw_arrow(arrowType, painter, option, upRect, arrowColor); + * } + * + * // The AddLine (down/right) button + * if (!transient && scrollBar->subControls & SC_ScrollBarAddLine) { + * if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) + * painter->setBrush(gradientStopColor); + * else if ((scrollBar->activeSubControls & SC_ScrollBarAddLine)) + * painter->setBrush(midColor2); + * else + * painter->setBrush(gradient); + * + * painter->setPen(Qt::NoPen); + * painter->drawRect(scrollBarAddLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0)); + * painter->setPen(QPen(alphaOutline, 1)); + * if (option->state & State_Horizontal) { + * painter->drawRect(scrollBarAddLine.adjusted(horizontal ? -1 : 0, 0, horizontal ? -1 : 0, horizontal ? -1 : 0)); + * } else { + * painter->drawRect(scrollBarAddLine.adjusted(0, horizontal ? 0 : -1, horizontal ? 0 : -1, horizontal ? 0 : -1)); + * } + * + * QRect downRect = scrollBarAddLine.adjusted(1, 1, -1, -1); + * painter->setPen(innerContrastLine()); + * painter->setBrush(Qt::NoBrush); + * painter->drawRect(downRect); + * + * Qt::ArrowType arrowType = Qt::DownArrow; + * if (option->state & State_Horizontal) + * arrowType = option->direction == Qt::LeftToRight ? Qt::RightArrow : Qt::LeftArrow; + * qt_fusion_draw_arrow(arrowType, painter, option, downRect, arrowColor); + * } + */ + + } + painter->restore(); + break; + default: + baseStyle()->drawComplexControl(cc, option, painter, widget); +#endif // QT_CONFIG(slider) +} +} + +private: + enum Direction { + TopDown, + FromLeft, + BottomUp, + FromRight + }; + + static QLinearGradient qt_fusion_gradient(const QRect &rect, const QBrush &baseColor, Direction direction = TopDown) + { + int x = rect.center().x(); + int y = rect.center().y(); + QLinearGradient gradient; + switch (direction) { + case FromLeft: + gradient = QLinearGradient(rect.left(), y, rect.right(), y); + break; + case FromRight: + gradient = QLinearGradient(rect.right(), y, rect.left(), y); + break; + case BottomUp: + gradient = QLinearGradient(x, rect.bottom(), x, rect.top()); + break; + case TopDown: + default: + gradient = QLinearGradient(x, rect.top(), x, rect.bottom()); + break; + } + if (baseColor.gradient()) + gradient.setStops(baseColor.gradient()->stops()); + else { + QColor gradientStartColor = baseColor.color().lighter(124); + QColor gradientStopColor = baseColor.color().lighter(102); + gradient.setColorAt(0, gradientStartColor); + gradient.setColorAt(1, gradientStopColor); + // Uncomment for adding shiny shading + // QColor midColor1 = mergedColors(gradientStartColor, gradientStopColor, 55); + // QColor midColor2 = mergedColors(gradientStartColor, gradientStopColor, 45); + // gradient.setColorAt(0.5, midColor1); + // gradient.setColorAt(0.501, midColor2); + } + return gradient; + } + + QColor highlightedOutline(const QPalette &pal) const { + QColor highlightedOutline = highlight(pal);//.darker(25);//QColor(Qt::green); + if (highlightedOutline.value() > 160) + highlightedOutline.setHsl(highlightedOutline.hue(), highlightedOutline.saturation(), 160); + return highlightedOutline; + } + + QColor calculateButtonColor(const QPalette &pal) const { + QColor buttonColor = pal.button().color(); + int val = qGray(buttonColor.rgb()); + buttonColor = buttonColor.lighter(100 + qMax(1, (180 - val)/6)); + buttonColor.setHsv(buttonColor.hue(), buttonColor.saturation() * 0.75, buttonColor.value()); + return buttonColor; + } + + // Used for grip handles + QColor lightShade() const { + return QColor(255, 255, 255, 90); + } + + QColor darkShade() const { + return QColor(0, 0, 0, 60); + } + + QColor innerContrastLine() const { + return QColor(255, 255, 255, 30); + } + + static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor = 50) { + const int maxFactor = 100; + QColor tmp = colorA; + tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor); + tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor); + tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor); + return tmp; + } + + + static void qt_fusion_draw_arrow(Qt::ArrowType type, QPainter *painter, const QStyleOption *option, const QRect &rect, const QColor &color) +{ + if (rect.isEmpty()) + return; + + const qreal dpi = calculateDpi(option); + const qreal dpr = painter->device()->devicePixelRatio(); + const int arrowWidth = int(dpiScaled(14, dpi)); + const int arrowHeight = int(dpiScaled(8, dpi)); + + const int arrowMax = qMin(arrowHeight, arrowWidth); + const int rectMax = qMin(rect.height(), rect.width()); + const int size = qMin(arrowMax, rectMax); + + QPixmap cachePixmap; + const QString cacheKey = "fusion-arrow"_L1 + QString::number(rect.size().width()) + QString::number(rect.size().height()) + QString::number(type); + + if (!QPixmapCache::find(cacheKey, &cachePixmap)) { + cachePixmap = styleCachePixmap(rect.size(), dpr); + QPainter cachePainter(&cachePixmap); + + QRectF arrowRect; + arrowRect.setWidth(size); + arrowRect.setHeight(arrowHeight * size / arrowWidth); + if (type == Qt::LeftArrow || type == Qt::RightArrow) + arrowRect = arrowRect.transposed(); + arrowRect.moveTo((rect.width() - arrowRect.width()) / 2.0, + (rect.height() - arrowRect.height()) / 2.0); + + std::array triangle; + switch (type) { + case Qt::DownArrow: + triangle = {arrowRect.topLeft(), arrowRect.topRight(), QPointF(arrowRect.center().x(), arrowRect.bottom())}; + break; + case Qt::RightArrow: + triangle = {arrowRect.topLeft(), arrowRect.bottomLeft(), QPointF(arrowRect.right(), arrowRect.center().y())}; + break; + case Qt::LeftArrow: + triangle = {arrowRect.topRight(), arrowRect.bottomRight(), QPointF(arrowRect.left(), arrowRect.center().y())}; + break; + default: + triangle = {arrowRect.bottomLeft(), arrowRect.bottomRight(), QPointF(arrowRect.center().x(), arrowRect.top())}; + break; + } + + cachePainter.setPen(Qt::NoPen); + cachePainter.setBrush(color); + cachePainter.setRenderHint(QPainter::Antialiasing); + cachePainter.drawPolygon(triangle.data(), int(triangle.size())); + + QPixmapCache::insert(cacheKey, cachePixmap); + } + + painter->drawPixmap(rect, cachePixmap); +} + +}; + + + +/* + * void MixerStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, + * QPainter *p, const QWidget *widget) const { + * switch (cc) { + * #if QT_CONFIG(slider) + * case CC_Slider: + * if (const QStyleOptionSlider *slider = qstyleoption_cast(opt)) { + * if (slider->subControls == SC_SliderTickmarks) { + * int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget); + * int ticks = slider->tickPosition; + * int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget); + * int len = proxy()->pixelMetric(PM_SliderLength, slider, widget); + * int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget); + * int interval = slider->tickInterval; + * if (interval <= 0) { + * interval = slider->singleStep; + * if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval, + * available) + * - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + * 0, available) < 3) + * interval = slider->pageStep; + * } + * if (!interval) + * interval = 1; + * int fudge = len / 2; + * int pos; + * // Since there is no subrect for tickmarks do a translation here. + * p->save(); + * p->translate(slider->rect.x(), slider->rect.y()); + * p->setPen(slider->palette.windowText().color()); + * int v = slider->minimum; + * while (v <= slider->maximum + 1) { + * if (v == slider->maximum + 1 && interval == 1) + * break; + * const int v_ = qMin(v, slider->maximum); + * pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, + * v_, available) + fudge; + * if (slider->orientation == Qt::Horizontal) { + * if (ticks & QSlider::TicksAbove) + * p->drawLine(pos, 0, pos, tickOffset - 2); + * if (ticks & QSlider::TicksBelow) + * p->drawLine(pos, tickOffset + thickness + 1, pos, + * slider->rect.height()-1); + * } else { + * if (ticks & QSlider::TicksAbove) + * p->drawLine(0, pos, tickOffset - 2, pos); + * if (ticks & QSlider::TicksBelow) + * p->drawLine(tickOffset + thickness + 1, pos, + * slider->rect.width()-1, pos); + * } + * // in the case where maximum is max int + * int nextInterval = v + interval; + * if (nextInterval < v) + * break; + * v = nextInterval; + * } + * } + * } + * p->restore(); + * break; + * default: + * baseStyle()->drawComplexControl(cc, opt, p, widget); + * break; + * } + * #endif // QT_CONFIG(slider) + * } + */ + +//void MixerStyle:: + diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 610f0b4..d264d01 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -1,42 +1,145 @@ -#include -#include - -//#include -//#include - -//#include -#include -#include - +//#include "contclasses.h" +#define INIT_FILELOG +#include "qtcommon.h" #include "qtclasses.h" -//TODO david #include "backlasses.h" +#include "qtvisuals.h" +#include "settings.h" +OverseerHandler *osh = nullptr; +ini::UserSettings *set = nullptr; -//INIT BACK -OverseerHandler *osh = new OverseerHandler(); - -QApplication* createApplication(int &argc, char *argv[]) -{ +bool startupRun = false; +bool onStartup = false; +char* userSettingsPath = nullptr; + +QApplication* createApplication(int &argc, char *argv[]) { return new QApplication(argc, argv); } +bool isInstanceRunning(QString appName) { + QLocalSocket socket; + socket.connectToServer(appName); + bool isOpen = socket.isOpen(); + socket.close(); + return isOpen; +} + +QLocalServer* startInstanceServer(QString appName) { + QLocalServer* server = new QLocalServer; + server->setSocketOptions(QLocalServer::WorldAccessOption); + server->listen(appName); + return server; +} + +void closeDebugFileLog() { + close_file_log_buffer(); +} + +void parseCmdArgs(int argc, char* argv[]) { + if(argc == 1) return; + //char* configPath = nullptr; + char* arg[] = { (char*)"--config-path=", (char*)"--change-startup" }; + + for (int i = 1; i < argc; i++) { + if(strstr(argv[i], arg[0])) { + userSettingsPath = argv[i] + strlen(arg[0]); + } + + if(strstr(argv[i], arg[1])) { + if (++i >= argc) return; + switch (argv[i][0]) { + case '0': + startupRun = true; + onStartup = false; + break; + case '1': + startupRun = true; + onStartup = true; + break; + default: + break; + } + return; + } + } + return; +} + +/* set_terminate + * void closeDebugFileLog2() { + * close_file_log_buffer(); + * abort(); + * } + */ 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); + /* + * Debug: logging report file + */ + initialize_file_log(); + atexit(closeDebugFileLog); + + QApplication::setStyle(new MixerStyle(QStyleFactory::create("Fusion"))); + //QApplication::setFont(font); + //QPalette palette = QGuiApplication::palette(); + + //palette.setColor(QPalette::Active, QPalette::Highlight, QColor(255, 192, 203, 200)); + //QColor(30,30,30,100)); + //QGuiApplication::setPalette(palette); + osh = new OverseerHandler(); + osh->populateSystemValues(); + + /* + * Arg parsing: (admin?) startup change run + */ + parseCmdArgs(argc, argv); + if (startupRun) { + if (!isInstanceRunning(PIPE_NAME)) exit(-1); + //if (startupChangeInfo->permissionChangeScope == NONE) exit(-1); + osh->updateStartupConfig(onStartup); + exit(0); } + + //Check if running + //https://stackoverflow.com/questions/48060989/qt-show-application-if-currently-running + if (!isInstanceRunning(PIPE_NAME)) + startInstanceServer(PIPE_NAME); + else exit(0); + + /* + * Config file init + */ + if (userSettingsPath) + set = ini::UserSettings::createSettings(userSettingsPath, true); + + if (set) + OverseerHandler::settingsPath = std::string(userSettingsPath); + else setConfigDirToDefaults(); + + StylingHelper::setBackgroundColor(osh->isLightMode()); + //qRegisterMetaType(); + + //INIT CONT + log_debugcpp("main init"); + osh->createEndpointHandlers(); + log_debugcpp("Reloaded endpoint handlers"); - osh->setEndpointHandlers(ephs); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); - MainWindow window = MainWindow(ephs); - //window.setEndpointHandlers(ephs); - app->setStyle("windowsvista"); - window.show(); + + MainWindow window = MainWindow(); + //window.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::QSizePolicy::MinimumExpanding) + QApplication::setQuitOnLastWindowClosed(false); + + DarkModeEventFilter* darkMode = new DarkModeEventFilter(); + QAbstractEventDispatcher::instance()->installNativeEventFilter(darkMode); + + /* + * QFile styleFile(":/assets/style.qss"); + * styleFile.open(QFile::ReadOnly); + * QString styleSheet { QLatin1String(styleFile.readAll()) }; + */ + return app->exec(); } + diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..f76e73c --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,231 @@ +#include "settings.h" +#include "msinclude.h" + +namespace ini { + + wchar_t* Utf8toUtf16(const char* str, uint64_t* size = nullptr) { + if(!str || str[0] == '\0') return nullptr; + int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if(size) *size = sizeNeeded; + wchar_t* utf16 = (wchar_t*)calloc(sizeNeeded, sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, str, -1, utf16, sizeNeeded); + return utf16; + } + + + + UserSettings::UserSettings(char* textContents) { + //Parsing values + bool isCRLF = false; + char *curLine = textContents; + char *separator = nullptr, *key = nullptr, *value = nullptr; + while(curLine) { + char* nextLine = strchr(curLine, '\n'); + if(nextLine == curLine + 1 || nextLine == curLine + 2) + goto nextIteration; + if (nextLine && (isCRLF || *(nextLine - 1) == '\r')) { + isCRLF = true; + *(nextLine - 1) = '\0'; + } else if (nextLine) *nextLine = '\0'; // temporarily terminate the current line + log_debugcpp("[SET] curLine: " + std::string(curLine) + " "); + + separator = strchr(curLine, '='); + if(!separator) + goto nextIteration; + *separator = '\0'; + key = trimAndAllocate(curLine); + value = trimAndAllocate(separator + 1); + values.try_emplace(key, value); + log_debugcpp("[SET] ini Map size: " + std::to_string(values.size())); + *separator = '='; + + nextIteration: + if (nextLine) { // then restore newline-char, just to be tidy + if (isCRLF) + *(nextLine - 1) = '\r'; + else *nextLine = '\n'; + } + curLine = nextLine ? (nextLine + 1) : NULL; + } + free(textContents); + } + + char* const UserSettings::getValue(char* key, uint64_t len) { + if (auto search = values.find(key); search != values.end()) + return (char* const) search->second; + return nullptr; + } + + void UserSettings::setValue(char* key, char* value, uint64_t valueSize, uint64_t keySize) { + char *newValue, *newKey; + if (auto search = values.find(key); search != values.end()) { + if(!(strcmp(value, search->second))) return; + newValue = (char*)calloc(valueSize, sizeof(char)); + if (!(search->second == pos || search->second == neg)) { + free(search->second); + } + search->second = newValue; + return; + } + + newValue = (char*)calloc(valueSize, sizeof(char)); + newKey = (char*)calloc(keySize, sizeof(char)); + values.insert(std::make_pair(newKey, newValue)); + return; + } + + void UserSettings::setValue(char* key, bool value, uint64_t keySize) { + char *newKey; + log_debugcpp("[SET] Pos value: " + std::to_string((intptr_t)pos)); + log_debugcpp("[SET] Neg value: " + std::to_string((intptr_t)neg)); + if (auto search = values.find(key); search != values.end()) { + log_debugcpp("[SET] Previous value: " + std::to_string((intptr_t)values[key])); + if (!(search->second == pos || search->second == neg)) { + free(search->second); + } + if (value) + search->second = pos; + else search->second = neg; + return; + } + + newKey = (char*)calloc(keySize, sizeof(char)); + memcpy(newKey, key, keySize * sizeof(char)); + values.insert(std::make_pair(newKey, value ? pos : neg)); + return; + } + + bool UserSettings::save(const char* path) { + if(!path) return false; + uint64_t convertedPathSize = 0; + wchar_t* utf16Path = Utf8toUtf16(path, &convertedPathSize); + if(!utf16Path) return false; + + #define releaseBeforeReturn() do { \ + CloseHandle(settingsHandle); \ + settingsHandle = nullptr; \ + free(utf16Path); \ + free(text); \ + } while(0) + + //We initially reserve 1024B for flushing. If storage is exceeded, more same-size chunks are allocated + const uint64_t chunkSize = 1024; + char* text = (char*)calloc(chunkSize, sizeof(char)); + uint64_t mapSize = values.size(); + uint64_t chunks = 1; + uint64_t keySize = 0, valueSize = 0, totalSize = 0, previousStepSize = 0; + //for(std::pair entry : values) { + std::unordered_map::iterator it; + for (it = values.begin(); it != values.end(); it++) { + keySize = strlen(it->first); + valueSize = strlen(it->second); + totalSize += valueSize + keySize + (it == values.begin() ? 1 : 2); //newline and separator + + if(totalSize > (chunkSize * chunks)) { + text = (char*)realloc(text, (++chunks * chunkSize)); + } + + if(it != values.begin()) + memcpy(text + previousStepSize++, "\n", sizeof(char)); + memcpy(text + previousStepSize, it->first, sizeof(char) * keySize); + memcpy(text + previousStepSize + keySize, "=", sizeof(char)); + memcpy(text + previousStepSize + 1 + keySize, it->second, sizeof(char) * valueSize); + + previousStepSize = totalSize; + } + + HANDLE settingsHandle = nullptr; + settingsHandle = CreateFile2( + utf16Path, + GENERIC_READ | GENERIC_WRITE, + 0, + CREATE_ALWAYS, + NULL); + if(settingsHandle == INVALID_HANDLE_VALUE) { + log_debugcpp("[SET] Can't save to file: " + std::to_string(GetLastError())); + releaseBeforeReturn(); + return false; + } + + DWORD bytesWritten; + BOOL writeSuccess = WriteFile( + settingsHandle, + text, + totalSize, + &bytesWritten, + nullptr + ); + + releaseBeforeReturn(); + if (writeSuccess == TRUE) return true; + else return false; + + return false; + #undef releaseBeforeReturn + } + + UserSettings::~UserSettings() { + //if(textContents) free(textContents); + for(std::pair entry : values) { + free(entry.first); + if (!(entry.second == pos || entry.second == neg)) + free(entry.second); + } + } + + UserSettings* UserSettings::createSettings(const char* path, bool create) { + if(!path) return nullptr; + wchar_t* utf16Path = Utf8toUtf16(path); + if(!utf16Path) return nullptr; + + #define releaseBeforeReturn() do { \ + CloseHandle(settingsHandle); \ + settingsHandle = nullptr; \ + free(utf16Path); \ + } while(0) + + char* textContents; + HANDLE settingsHandle = nullptr; + settingsHandle = CreateFile2( + utf16Path, + GENERIC_READ | GENERIC_WRITE, + 0, + (create ? OPEN_ALWAYS : OPEN_EXISTING), + NULL); + if(settingsHandle == INVALID_HANDLE_VALUE) { + std::string createString = std::string(create ? (char*)"create" : (char*)"open"); + log_debugcpp("[SET] Can't create settings file: " + std::to_string(GetLastError())); + log_debugcpp("[SET] Can't " + createString + \ + " settings file on: " + std::string(path) + \ + ", error: " + std::to_string(GetLastError())); + releaseBeforeReturn(); + return nullptr; + } + + //Calculating file size and reading file + uint64_t fileSize; + LARGE_INTEGER fileSizeStruct; + if(!GetFileSizeEx(settingsHandle, &fileSizeStruct)) { + releaseBeforeReturn(); + return nullptr; + } + fileSize = fileSizeStruct.QuadPart; + + uint32_t bytesRead = 0; + uint64_t textContentsSize = fileSize + 1; + textContents = (char*)calloc(textContentsSize, sizeof(char)); + if (ReadFile(settingsHandle, textContents, fileSize, + (LPDWORD)&bytesRead, NULL) != TRUE) { + releaseBeforeReturn(); + return nullptr; + } + + releaseBeforeReturn(); + return new UserSettings(textContents); + + //textContents.assign(tempTextContents); + //free(tempTextContents); + #undef releaseBeforeReturn + } + +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..9974507 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,71 @@ +#pragma once +#include "global.h" + +namespace ini { + + //Trims spaces, LF and CRLF + static inline char* trimAndAllocate(const char* in, uint64_t len = 0) { + if (!in) return nullptr; + + uint64_t startingPos = 0, lastPos = 0; + bool foundStart = false; + for(int i = 0; ;i++) { + char c = in[i]; + if ((len > 0 && startingPos == (len - 1)) || (len > 0 && lastPos == (len - 1))) return nullptr; + if ((c != ' ' || c != '\r' || c != '\n') && !foundStart) { + foundStart = true; + lastPos = startingPos; + } + if (foundStart && (c == ' ' || c == '\r' || c == '\n')) { + break; + } + if(!foundStart) + startingPos++; + else lastPos++; + } + if(!(lastPos - startingPos)) return nullptr; + + char* trimmedString = (char*)calloc(lastPos - startingPos + 1, sizeof(char)); + memcpy(trimmedString, in + startingPos, lastPos - startingPos); + return trimmedString; + } + + struct Djb12Hasher { + size_t operator()(char* str) const { + unsigned long hash = 5381; + int c; + + while (c = *str++) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + + return hash; + } + }; + + struct StrcmpEqual { + bool operator()(char* key1, char* key2) const { + return (!strcmp(key1, key2)); + } + }; + +class UserSettings { + + public: + static UserSettings* createSettings(const char* path = nullptr, bool create = false); + ~UserSettings(); + + char* const getValue(char* key, uint64_t len = 0); + void setValue(char* key, char* value, uint64_t valueSize, uint64_t keySize); //'\0' included + void setValue(char* key, bool value, uint64_t keySize); //'\0' included + + bool save(const char* path); + protected: + + private: + UserSettings(char* text = nullptr); + std::unordered_map values; + char* pos = "true"; + char* neg = "false"; +}; + +}