diff --git a/.gitignore b/.gitignore index 9eec94c..a4ce7f0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ 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 index a9cbd38..2206a8a 100644 --- a/assets.qrc +++ b/assets.qrc @@ -1,7 +1,10 @@ + assets/selawk.ttf assets/notificationAreaIcon.png assets/style.qss assets/logo.ico + assets/mute.svg + assets/unmute.svg diff --git a/assets/SoundVolumeView.exe b/assets/SoundVolumeView.exe deleted file mode 100644 index 4c7a7d2..0000000 Binary files a/assets/SoundVolumeView.exe and /dev/null differ 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/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/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 edb534d..540be60 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,5 +1,7 @@ -taskkill /F /IM "qtest.exe" -qmake -o build\Makefile .\qtest.pro -copy /Y /B .\assets\SoundVolumeView.exe .\build\debug -copy /Y /B .\assets\SoundVolumeView.exe .\build\release -mingw32-make.exe -C .\build -f Makefile +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 707ff2d..42a1165 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,18 +1,28 @@ -QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview -QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v -LIBS += -LC:/capybara/libclang/x86_64-w64-mingw32/lib -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 -lpropsys -#"kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 -DEFINES += DEBUG QT_LOGGING_TO_CONSOLE=1 WIN32_LEAN_AND_MEAN -CONFIG += debug +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= +} -QT += widgets network +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" -DESTPATH += "$$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" +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 -HEADERS += qtclasses.h backlasses.h backsessionclasses.h contclasses.h contsessionclasses.h global.h debug.h backfuncs.h ipolicyconfig.h msinclude.h -RESOURCES = assets.qrc +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 - -#DESTDIR += "build" diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index f5b4fa7..74af6a0 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,5 +1,7 @@ -#include -#include +#include "backlasses.h" +#include "backfuncs.h" + +using namespace Environment; EndpointNewSessionCallback::EndpointNewSessionCallback(EndpointHandler* eph){ this->eph = eph; @@ -39,23 +41,27 @@ HRESULT EndpointNewSessionCallback::QueryInterface(REFIID riid, VOID **ppvInterf HRESULT EndpointNewSessionCallback::OnSessionCreated(IAudioSessionControl *NewSession) { if (eph->getFlow() == Flows::FLOW_CAPTURE) return S_OK; - HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); IAudioSessionControl2* sessionControl; //ISimmpleAudioVolume* sessionVolume; if (FAILED(NewSession->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl))) { log_wdebugcpp(L"no nueva sesion......"); }; if (sessionControl) { - sessionControl->AddRef(); - //sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); Session* newSession = new Session(this->eph->getEndpoint(), sessionControl); - eph->addSessionSendFront(newSession); + + SessionThreadParams tp = { .eph = this->eph, .session = newSession, .isDelete = false }; + wait = true; + std::thread newSessionThread(&EndpointNewSessionCallback::createSessionThread, this, tp); + newSessionThread.detach(); + while(wait); } - if (result == S_OK) - CoUninitialize(); - return S_OK; } +void EndpointNewSessionCallback::createSessionThread(SessionThreadParams params) { + params.eph->addSessionSendFront(params.session); + this->wait = false; +} + EndpointVolumeCallback::EndpointVolumeCallback(Endpoint* ep){ this->ep = ep; } @@ -94,46 +100,69 @@ HRESULT EndpointVolumeCallback::QueryInterface(REFIID riid, VOID **ppvInterface) HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - //delete osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; - //osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.freeData4(); - //Could've made a function or = override to hide this within Nguid, but back in cont = bad. - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ - = pNotify->guidEventContext.Data1; - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ - = pNotify->guidEventContext.Data2; - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ - = pNotify->guidEventContext.Data3; - for(int i = 0; i < 8 /* Data4 size */; i++){ - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = pNotify->guidEventContext.Data4[i]; + 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]; } - - //memcpy(&osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - Flows flow = this->ep->getFlow(); - EndpointHandler* eph = nullptr; - if (flow & Flows::FLOW_PLAYBACK) { - eph = osh->getPlaybackEndpointHandlers().at(this->ep->getIndex()); - } else { - eph = osh->getCaptureEndpointHandlers().at(this->ep->getIndex()); - } - - eph->getCallbackInfo()->muted = pNotify->bMuted; - eph->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; - eph->getCallbackInfo()->channels = pNotify->nChannels; - - - UINT j = 0; - //todo: do while here caused stack corruption; sus - while(j < pNotify->nChannels) { - if (flow & Flows::FLOW_PLAYBACK) - eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; - else - eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; - j++; - } + 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; @@ -173,6 +202,7 @@ HRESULT EndpointSituationCallback::QueryInterface(REFIID riid, VOID **ppvInterfa 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) { @@ -188,8 +218,7 @@ HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole } std::wstring wstringEndpointId = pwstrDeviceId; log_wdebugcpp(L"we got za defol 4 " + wstringEndpointId); - osh->changeFrontDefaultsCallback(nRole, wstringEndpointId); - + osh->roleBucketEntryCallback(nRole, wstringEndpointId); return S_OK; } @@ -205,39 +234,54 @@ HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { std::wstring endpointId = std::wstring(pwstrDeviceId); - switch (dwNewState){ + log_wdebugcpp(L"Endpoint state change for " + endpointId); + EndpointState newState; + switch (dwNewState) { case DEVICE_STATE_ACTIVE: - osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_ACTIVE); + newState = EndpointState::ENDPOINT_ACTIVE; break; case DEVICE_STATE_DISABLED: - osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_DISABLED); + newState = EndpointState::ENDPOINT_DISABLED; break; case DEVICE_STATE_NOTPRESENT: - osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_NOTPRESENT); + newState = EndpointState::ENDPOINT_NOTPRESENT; break; case DEVICE_STATE_UNPLUGGED: - osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_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) { - os->updateEndpointInfo(std::wstring(pwstrDeviceId)); + isEpStateChanging.exchange(true); + std::thread propertyThread(&Overseer::updateEndpointInfo, os, std::wstring(pwstrDeviceId)); + propertyThread.detach(); + while(isEpStateChanging); return S_OK; } -Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ +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 */ - //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas - if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-2);}; + DWORD state; + if(FAILED(endpoint->GetState(&state))) {exit(-2);}; + this->endpointState = (EndpointState)state; if(this->endpointState == EndpointState::ENDPOINT_ACTIVE) { activateEndpointVolume(); @@ -248,11 +292,9 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ } - reloadEndpointChannels(); - //todo: atexit into exit Gather ID LPWSTR tempString = nullptr; - if (FAILED(endpoint->GetId(&tempString))) {exit(-1);}; + if (FAILED(endpoint->GetId(&tempString))) { exit(-1); }; endpointId = std::wstring(tempString); log_wdebugcpp(endpointId); CoTaskMemFree(tempString); @@ -260,16 +302,14 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ endpoint->OpenPropertyStore(STGM_READ, &properties); this->updateName(); this->setFlow(); - if (this->flow == Flows::FLOW_PLAYBACK) { - activateEndpointSessions(); - log_debugcpp("plays back"); - } + + reloadEndpointChannels(); } void Endpoint::updateName() { PROPVARIANT pv; #define store_name(key, propvariant, wstr) do { \ - properties->GetValue(key , &propvariant); \ + properties->GetValue(key, &propvariant); \ if (pv.pwszVal == nullptr) wstr = L"Unnamed Not Present Endpoint"; \ else wstr = std::wstring(pv.pwszVal); \ } while (0) @@ -282,27 +322,34 @@ void Endpoint::updateName() { } void Endpoint::activateEndpointSessions() { - //sessionManager; - if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**) &sessionManager))) { log_wdebugcpp(L"sesionbros..."); return; } - + 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..."); return; } + 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++) { + for (int i = 0; i < sessionCount; i++) { IAudioSessionControl* sessionControlTmp; - sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp); - /*todo: borrar when donezo - * float test2; - * IAudioMeterInformation* ttmp = (IAudioMeterInformation*)sessionControlTmp; - * ttmp->GetPeakValue(&test2); - */ - //todo:: asegurar lo del dynamic_cast + if (FAILED(sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp))) { + exit(-6); + } IAudioSessionControl2* sessionControl; - sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl); - sessionControl->AddRef(); + 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; @@ -311,34 +358,38 @@ void Endpoint::activateEndpointSessions() { 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() { - //bool extraThread = false; - /* - * Forgive me, for MS has sinned, and now I must too. - */ - if (this->endpointVolume == nullptr){ - HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); - endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume); + //If 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 (endpointVolume == nullptr) { //why they returning 0 after dealing with the error jfc CO_E_NOTINITIALIZED) { - //CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); - //extraThread = true; - //goto initialized; - //} - //log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); - if (result == S_OK) - CoUninitialize(); + CLSCTX_ALL, NULL, (void**)&endpointPeakMeter))) { + log_debugcpp("peakbros..."); + } } + if (result == S_OK) + CoUninitialize(); } void Endpoint::reloadEndpointChannels() { @@ -391,7 +442,7 @@ bool Endpoint::getMute(){ return mute; } -void Endpoint::setState(uint8_t state){ +void Endpoint::setState(EndpointState state){ this->endpointState = state; if(state == EndpointState::ENDPOINT_ACTIVE) { this->activateEndpointVolume(); @@ -399,7 +450,7 @@ void Endpoint::setState(uint8_t state){ } } -size_t Endpoint::getState(){ +EndpointState Endpoint::getState(){ return this->endpointState; } @@ -407,9 +458,13 @@ 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))) {}; + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { + log_wdebugcpp(L"Master volume failed for endpoint: " + friendlyName); + }; } else { - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) {}; + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { + log_wdebugcpp(L"Channel " + std::to_wstring(channel) + L" volume failed for endpoint: " + friendlyName); + }; } } @@ -434,70 +489,29 @@ Roles Endpoint::getRoles(){ } void Endpoint::setRoles(Roles role){ - //todo: otro exe momento - STARTUPINFOEXW startupConfig; - PROCESS_INFORMATION processInfo; - SecureZeroMemory(&startupConfig, sizeof(STARTUPINFOEXW)); - SecureZeroMemory(&startupConfig.StartupInfo, sizeof(STARTUPINFOW)); - startupConfig.StartupInfo.cb = sizeof(STARTUPINFOEXW); - SecureZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); + if (!policyConfig) return; - std::wstring command = L"SoundVolumeView.exe /SetDefault " + endpointId + L" "; - std::wstring troublePair = L"1"; - switch (role) { - case Roles::ROLE_ALL: - /* - * console or multimedia, one sends both, at least for now; - * either cos of ms or dis guy; - * no choice but to treat them as one for now. - * command += L"all"; and nothing else would've been nice... - */ - troublePair = command + troublePair; - if(CreateProcessW( - NULL, - (wchar_t*)troublePair.c_str(), - NULL, - NULL, - false, - CREATE_UNICODE_ENVIRONMENT, - NULL, - NULL, - (LPSTARTUPINFOW)&startupConfig, - &processInfo - ) == true) { - WaitForSingleObject(processInfo.hProcess, INFINITE ); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } - command += L"2"; - break; + bool allRoles = false; + ERole val; + switch(role) { case Roles::ROLE_CONSOLE: - command += std::to_wstring(0); + val = eConsole; break; case Roles::ROLE_MULTIMEDIA: - command += std::to_wstring(1); + val = eMultimedia; break; case Roles::ROLE_COMMUNICATIONS: - command += std::to_wstring(2); + val = eCommunications; + break; + default: + allRoles = true; break; } - - if(CreateProcessW( - NULL, - (wchar_t*)command.c_str(), - NULL, - NULL, - false, - CREATE_UNICODE_ENVIRONMENT, - NULL, - NULL, - (LPSTARTUPINFOW)&startupConfig, - &processInfo - ) == true) { - WaitForSingleObject(processInfo.hProcess, INFINITE ); - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - } + 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){ @@ -512,11 +526,10 @@ void Endpoint::removeRoles(Roles role){ void Endpoint::setFlow() { IMMEndpoint* flowGetter; - //this should be as simple as writing IID_IMMEndpoint, but it just won't find the macro, so I reimpl it. Sad. if(FAILED(this->endpoint->QueryInterface(__uuidof(IMMEndpoint), (void**)&flowGetter))) { log_debugcpp("no flow..."); } EDataFlow MSflow; - HRESULT vafllar = flowGetter->GetDataFlow(&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(); @@ -532,7 +545,9 @@ std::vector Endpoint::getSessions() { } size_t Endpoint::getSessionCount() { - return endpointSessions.size(); + size_t sessionCount; + sessionCount = endpointSessions.size(); + return sessionCount; } void Endpoint::registerNewSessionNotification(EndpointNewSessionCallback* ensc){ @@ -543,18 +558,28 @@ 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() { - OutputDebugStringW(L"EPWidget creation\n"); if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { - log_debugcpp("si"); }; + log_debugcpp("Not even COM?"); }; //Retrieving endpoint enumerator @@ -562,21 +587,23 @@ 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); - //if(FAILED(CoCreateInstance(__uuidof(CPolicyConfigClient), - // NULL, CLSCTX_ALL, __uuidof(IPolicyConfig10), (LPVOID *)&policyConfig))) {exit(-1);} + 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(Flows flow) { +void Overseer::createEndpoints(Flows flow) { IMMDeviceCollection *deviceCollection; unsigned int numEndpoints; EDataFlow MSflow = (flow == Flows::FLOW_PLAYBACK ? EDataFlow::eRender : EDataFlow::eCapture); @@ -588,8 +615,8 @@ void Overseer::reloadEndpoints(Flows flow) { /* * Counting them */ - if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; - if(numEndpoints == 0) { log_debugcpp("si"); }; + if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; + if(numEndpoints == 0) { log_debugcpp("si"); }; /* * Retrieving actual endpoints and storing them on their own collection @@ -597,7 +624,7 @@ void Overseer::reloadEndpoints(Flows flow) { IMMDevice *temp; for (unsigned int i = 0; i < numEndpoints; i++){ if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; - Endpoint *endpoint = new Endpoint(temp, i); + Endpoint *endpoint = new Endpoint(temp, policyConfig, i); if (flow == Flows::FLOW_PLAYBACK) this->playbackDevices.push_back(endpoint); else @@ -625,6 +652,8 @@ void Overseer::reloadEndpoints(Flows flow) { break; } deviceEnumerator->GetDefaultAudioEndpoint(MSflow, val, &temp); + if (!temp) continue; + LPWSTR id = nullptr; if (flow == Flows::FLOW_PLAYBACK) { @@ -654,11 +683,17 @@ void Overseer::reloadEndpoints(Flows flow) { } 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."); + log_debugcpp("ay caramba con la hot metida. Sad!"); - Endpoint *endpoint = new Endpoint(newep); + Endpoint *endpoint = new Endpoint(newep, policyConfig); Flows getFlow = endpoint->getFlow(); if (getFlow == Flows::FLOW_PLAYBACK) { @@ -669,25 +704,179 @@ Endpoint* Overseer::addEndpoint(std::wstring endpointId, /* out */Flows* flow = this->captureDevices.push_back(endpoint); } if (flow != nullptr) *flow = getFlow; + CoUninitialize(); return endpoint; } -Overseer::Overseer() : epsc(this){ - //Initializing COM library +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(Flows::FLOW_PLAYBACK); + //Obtaining playback endpoint collection + createEndpoints(Flows::FLOW_PLAYBACK); //reloadEndpoints(Flows::FLOW_CAPTURE); +} - //Registering for endpoint information callback - //this->epsc.fill(deviceEnumerator, playbackDevices, captureDevices); - //this->epsc.fill(deviceEnumerator); +NGuid Overseer::getGuid() { + return guid; +} + +void Overseer::registerEndpointSituationCallback() { if(FAILED(deviceEnumerator->RegisterEndpointNotificationCallback(((IMMNotificationClient*)&epsc)))) { log_debugcpp("when no enchufas......"); } } -void Overseer::openControlPanel() { +std::vector Overseer::getPlaybackEndpoints() { + return playbackDevices; +} + +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)); @@ -713,36 +902,146 @@ void Overseer::openControlPanel() { } } -NGuid Overseer::getGuid() { - return guid; -} - -std::vector Overseer::getPlaybackEndpoints() { - return playbackDevices; -} - -std::vector Overseer::getCaptureEndpoints() { - return captureDevices; -} - -void Overseer::updateEndpointInfo(std::wstring endpointId) { - log_wdebugcpp(L"new name Endpoint id: " + endpointId); - //todo: reintroduce capture devices - for(auto ep : playbackDevices) { - if (ep->getId() == endpointId) { - ep->updateName(); - osh->updateFrontEndpointName(ep); - break; - } +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 } -Overseer::~Overseer(){ - log_debugcpp("cum"); - deviceEnumerator->Release(); - for(unsigned long long i = 0; i < playbackDevices.size(); i++){ - delete(playbackDevices.at(i)); +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 f2c0199..5acedba 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -4,14 +4,50 @@ #include "backsessionclasses.h" #include "global.h" #include "contclasses.h" +//#include "environment.h" class EndpointVolumeCallback; class Session; +// 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, uint64_t idx = 0); + Endpoint(IMMDevice* endpoint, IPolicyConfig7* policyConfig, uint64_t idx = 0); //todo: how to forward declare delegate constructors? //Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; void reloadEndpointChannels(); @@ -22,8 +58,8 @@ class Endpoint { float getVolume(int channel); void setMute(NGuid guid, bool muted); bool getMute(); - void setState(uint8_t state); - size_t getState(); + void setState(EndpointState state); + EndpointState getState(); Roles getRoles(); void setRoles(Roles role); void assignRoles(Roles role); @@ -44,32 +80,34 @@ class Endpoint { void addSession(Session* session); void registerNewSessionNotification(EndpointNewSessionCallback* ensc); void unregisterNewSessionNotification(EndpointNewSessionCallback* ensc); - + void deleteSessions(); + void activateEndpointSessions(); + //void deleteSessionManager(); + std::mutex endpointSessionsMutex; ~Endpoint(); private: void inline activateEndpointVolume(); - void inline activateEndpointSessions(); std::vector endpointSessions; uint32_t channelCount = 0; IMMDevice *endpoint; - IAudioClient *audioClient; - int64_t defTime, minTime; - IAudioSessionManager2 *sessionManager; - Flows flow; - IAudioEndpointVolume *endpointVolume = nullptr; + IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; + 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; - unsigned long endpointState; + EndpointState endpointState; Roles endpointRoles = (Roles)0; uint64_t idx; //Not implemented in llvm-mingw. Sad! todo: mingw patch - IAudioMeterInformation *endpointPeakMeter = nullptr; - + IPolicyConfig7* policyConfig; }; class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { @@ -81,11 +119,14 @@ class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { 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 { @@ -99,24 +140,31 @@ class EndpointSituationCallback : public IMMNotificationClient { 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 { public: Overseer(); - void openControlPanel(); + void registerEndpointSituationCallback(); + NGuid getGuid(); + std::vector getPlaybackEndpoints(); std::vector getCaptureEndpoints(); void updateEndpointInfo(std::wstring endpointId); - void reloadEndpoints(Flows flow); + void createEndpoints(Flows flow); Endpoint* addEndpoint(std::wstring endpointId, /* out */ Flows* flow); - NGuid getGuid(); + + void reportFinishedStateChange(); + + std::mutex playbackMutex; + std::mutex captureMutex; //void setEndpointStatusCallback(); //void setEndpointStatusCallback(); @@ -128,28 +176,65 @@ class Overseer { ~Overseer(); private: + void initCOMLibrary(); + NGuid guid; IMMDeviceEnumerator *deviceEnumerator; EndpointSituationCallback epsc; - //IPolicyConfig *policyConfig; + std::vector playbackDevices; std::vector captureDevices; - void initCOMLibrary(); + 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 index 80eeac8..c9aaa85 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -37,6 +37,9 @@ HRESULT SessionStateCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { } 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; @@ -91,8 +94,10 @@ HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { } HRESULT SessionStateCallback::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) { - sh->setState(SessionState::DISCONNECTED); - sh->reviseSessionShowing(SessionState::DISCONNECTED); + if (DisconnectReason != DisconnectReasonDeviceRemoval) { + sh->setState(SessionState::DISCONNECTED); + sh->reviseSessionShowing(SessionState::DISCONNECTED); + } return S_OK; } @@ -126,10 +131,19 @@ Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx else { LPWSTR sessionDisplayName; this->sessionControl->GetDisplayName(&sessionDisplayName); - if (!wcscmp(sessionDisplayName, L"")) - this->sessionName = this->fetchProcessName(pid); - else + 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); } } @@ -192,87 +206,196 @@ void Session::setMute(NGuid guid, bool muted) { if(FAILED(sessionVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"SessionVolume null?")); }; } -std::wstring Session::fetchProcessName(DWORD pid) { - /* - * https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot - * https://stackoverflow.com/questions/11843368/how-to-get-process-description - */ - - /* Executable path retrieval */ - std::wstring exePath = L""; - - HANDLE processList = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); - if (processList == INVALID_HANDLE_VALUE) { - log_wdebugcpp(L"aye no procname."); - return exePath; +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; } - MODULEENTRY32W me32w; - me32w.dwSize = sizeof(MODULEENTRY32W); - if(Module32FirstW(processList, &me32w)) { - do { - if (me32w.th32ProcessID == pid) { - exePath = std::wstring(me32w.szExePath); - break; - /* - * However, if the calling process is a 32-bit process, you must call the - * QueryFullProcessImageName function to retrieve the full path of the - * executable file for a 64-bit process. - */ - } - } while(Module32NextW(processList, &me32w)); - } - CloseHandle(processList); + *exePath = std::wstring(fileName); + return true; +} - /* File description retrieval */ +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 = GetFileVersionInfoSizeW(exePath.c_str(), &filler); - if (!fileVersionInfoSize) return exePath; + DWORD fileVersionInfoSize = GetFileVersionInfoSizeExW + (FILE_VER_GET_LOCALISED | FILE_VER_GET_NEUTRAL, exePath.c_str(), &filler); + if (!fileVersionInfoSize) return false; void* fileVersionInfo = malloc(fileVersionInfoSize); - if(!GetFileVersionInfoW(exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) - return exePath; + 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)) - return exePath; + 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; } - bool match = false; - for (UINT i = 0; i < (translationArrayLen / sizeof(LANGANDCODEPAGE)); i++) { - wchar_t fileDescriptionKey[256]; + 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; - match = true; - wchar_t* fileDescription = NULL; - UINT fileDescriptionSize = 0; - swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", - translationArray[i].wLanguage, translationArray[i].wCodePage); - if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { - exePath = std::wstring(fileDescription); - } + syslangIdx = i; + break; } - if (!match && 1 <= (translationArrayLen / sizeof(LANGANDCODEPAGE))) { - wchar_t fileDescriptionKey[256]; + 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; + } -wchar_t* fileDescription = NULL; - UINT fileDescriptionSize = 0; - swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", - translationArray[0].wLanguage, translationArray[0].wCodePage); - if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { - exePath = std::wstring(fileDescription); - } - } + 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; - free(fileVersionInfo); - return exePath; } //todo: conflicting names. change callback name @@ -296,4 +419,7 @@ 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 index 2b5db99..ae87f0a 100644 --- a/src/back/backsessionclasses.h +++ b/src/back/backsessionclasses.h @@ -47,13 +47,18 @@ class Session { //uint32_t getChannelCount(); private: - std::wstring fetchProcessName(DWORD pid); + 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; + size_t idx; }; diff --git a/src/back/ipolicyconfig.h b/src/back/ipolicyconfig.h index 2e10aab..7b474bb 100644 --- a/src/back/ipolicyconfig.h +++ b/src/back/ipolicyconfig.h @@ -1,47 +1,42 @@ -// ---------------------------------------------------------------------------- -// PolicyConfig.h -// Undocumented COM-interface IPolicyConfig. -// Use for set default audio render endpoint -// @author EreTIk -// ---------------------------------------------------------------------------- +#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 -#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 -{ +/***************************************************************************** + * 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 ** @@ -107,32 +102,69 @@ public: 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) */ + -/* 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} */ +/* // 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 IPolicyConfigVista */ -/* // {568b9108-44bf-40b4-9006-86afe5b5a620} */ +/* // interface IPolicyConfig */ +/* // {f8679f50-850a-41cf-9c72-430f290290c8} */ /* // */ /* // Query interface: */ -/* // CComPtr PolicyConfig; */ -/* // PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient)); */ +/* // CComPtr PolicyConfig; */ +/* // PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); */ /* // */ -/* // @compatible: Windows Vista and Later */ +/* // @compatible: Windows 7 and Later */ /* // ---------------------------------------------------------------------------- */ -/* interface IPolicyConfigVista : public IUnknown */ +/* interface IPolicyConfig : public IUnknown */ /* { */ /* public: */ /* virtual HRESULT GetMixFormat( */ /* PCWSTR, */ /* WAVEFORMATEX ** */ -/* ); // not available on Windows 7, use method from IPolicyConfig */ +/* ); */ /* virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( */ /* PCWSTR, */ @@ -140,6 +172,10 @@ public: /* WAVEFORMATEX ** */ /* ); */ +/* virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( */ +/* PCWSTR */ +/* ); */ + /* virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( */ /* PCWSTR, */ /* WAVEFORMATEX *, */ @@ -151,22 +187,22 @@ public: /* 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, */ @@ -181,12 +217,95 @@ public: /* ); */ /* virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( */ -/* __in PCWSTR wszDeviceId, */ -/* __in ERole eRole */ +/* PCWSTR wszDeviceId, */ +/* ERole eRole */ /* ); */ /* virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( */ /* PCWSTR, */ /* INT */ -/* ); // not available on Windows 7, use method from IPolicyConfig */ +/* ); */ /* }; */ + +/* /\* 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 index 9911165..52fccbd 100644 --- a/src/back/msinclude.h +++ b/src/back/msinclude.h @@ -1,12 +1,15 @@ #pragma once -#define _WIN32_WINNT 0x0A00 #include //done by qt by def #define UNICODE #include #include +#include +#include +#include +#include #include #include #include @@ -14,6 +17,7 @@ #include #include #include +#include //#include #include @@ -23,10 +27,11 @@ //#include #include #include -#include "ipolicyconfig.h" #include #include +#include +#include "ipolicyconfig.h" #include "audiometerinfo.h" // IAudioMeterInformation 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 f46571c..d2188f0 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,27 +1,37 @@ #include "backlasses.h" #include "contclasses.h" -//TODO: pragma once + +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 +} EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { - //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); this->idx = idx; this->flow = flow; this->ep = (flow == Flows::FLOW_PLAYBACK ? osh->getPlaybackEndpoints().at(idx) : osh->getCaptureEndpoints().at(idx)); - epc = new EndpointVolumeCallback(ep); - ensc = new EndpointNewSessionCallback(this); this->callbackInfo.caller = osh->getGuid(); - ep->registerNewSessionNotification(ensc); //epName = ep->getName(); this->setBackEndpointVolumeCallbackInfoContent(this->getState()); osh->pushBackEndpointHandler(this, flow); - - if (this->flow == Flows::FLOW_PLAYBACK) { - for (int i = 0; i < this->getSessionCount(); i++) { - SessionHandler* sessionHandler = new SessionHandler(this, this->getSessions().at(i),i); - sessionHandlers.push_back(sessionHandler); - } - } } void OverseerHandler::pushBackEndpointHandler(EndpointHandler* eph, Flows flow) { @@ -50,16 +60,6 @@ Flows EndpointHandler::getFlow(){ return ep->getFlow(); } -/* these two, currently unused. If I use them, I should feel bad. - * Endpoint* EndpointHandler::getEndpoint() { - * return this->ep; - * } - * - * EndpointVolumeCallback* EndpointHandler::getEndpointVolumeCallback() { - * return this->epc; - * } - */ - BackEndpointVolumeCallbackInfo* EndpointHandler::getCallbackInfo(){ return &this->callbackInfo; } @@ -114,7 +114,10 @@ void EndpointHandler::setBackEndpointVolumeCallbackInfoContent(uint8_t state) { callbackInfo.muted = this->getMute(); callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); callbackInfo.channels = this->getChannelCount(); - ep->setVolumeCallback(epc); + 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); @@ -122,12 +125,12 @@ void EndpointHandler::setBackEndpointVolumeCallbackInfoContent(uint8_t state) { } } -void EndpointHandler::setState(uint8_t state){ +void EndpointHandler::setState(EndpointState state){ ep->setState(state); this->setBackEndpointVolumeCallbackInfoContent(state); } -void EndpointHandler::setState(uint8_t state, uint64_t index){ +void EndpointHandler::setState(EndpointState state, uint64_t index){ ep->setState(state); this->setFrontVisibilityInfo((EndpointState)state, index); this->setBackEndpointVolumeCallbackInfoContent(state); @@ -179,10 +182,15 @@ Endpoint* EndpointHandler::getEndpoint() { } void EndpointHandler::addSessionSendFront(Session* session) { - ep->addSession(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); } @@ -194,6 +202,49 @@ 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); @@ -205,8 +256,44 @@ 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() { - this->os->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() { @@ -233,7 +320,7 @@ uint64_t OverseerHandler::getCaptureEndpointsCount(){ return this->os->getCaptureEndpoints().size(); } -void OverseerHandler::reloadEndpointHandlers(){ +void OverseerHandler::createEndpointHandlers(){ //todo: add capture //std::vector* ephs = new std::vector; @@ -241,9 +328,7 @@ void OverseerHandler::reloadEndpointHandlers(){ for(uint64_t i = 0; i < this->getPlaybackEndpointsCount(); i++){ log_debugcpp("Creating Playback handler " + std::to_string(i)); - - EndpointHandler* ephexx = new EndpointHandler(i, Flows::FLOW_PLAYBACK); - //this->playbackEndpointHandlers.push_back(ephexx); + 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())); } @@ -251,51 +336,55 @@ void OverseerHandler::reloadEndpointHandlers(){ log_debugcpp("Capture VSize: " + std::to_string(this->getCaptureEndpointsCount())); - for(uint64_t i = 0; i < this->getCaptureEndpointsCount(); i++){ + for(uint64_t i = 0; i < this->getCaptureEndpointsCount(); i++) { log_debugcpp("Creating Capture handler " + std::to_string(i)); - - /* - * if(i < (this->captureEndpointHandlers.size()) && - * this->captureEndpointHandlers.at(i) != nullptr) - * delete captureEndpointHandlers.at(i); - */ - - EndpointHandler* ephoo = new EndpointHandler(i, Flows::FLOW_CAPTURE); - //this->captureEndpointHandlers.push_back(ephoo); + 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; */ } - //setEndpointHandlers(ephs); + os->registerEndpointSituationCallback(); } -EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId, /* out */ Flows *flow = nullptr){ +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); - // std::vector getPlaybackEndpointHandlers(); - //std::vector getCaptureEndpointHandlers(); 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::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::changeFrontDefaultsCallback(Roles role, std::wstring endpointId) { - this->changeFrontDefaults(role, endpointId); +void OverseerHandler::setRoleBucketEntryFunction(std::function roleBucketEntry) { + this->roleBucketEntry = roleBucketEntry; } void OverseerHandler::updateFrontEndpointName(Endpoint* ep) { @@ -306,7 +395,12 @@ void OverseerHandler::updateFrontEndpointName(Endpoint* ep) { } 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; @@ -320,20 +414,29 @@ void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointSta //debug Flows flow; if (!eph) { - if (state ^ EndpointState::ENDPOINT_ACTIVE) return; + 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) return; + if (flow == Flows::FLOW_CAPTURE) goto end; - if(eph && EndpointState::ENDPOINT_ACTIVE & state) { + if (eph && EndpointState::ENDPOINT_ACTIVE & state) { + eph->setState(EndpointState::ENDPOINT_ACTIVE); this->addEndpointWidget(eph); - } else if (eph && eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ + } 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; } @@ -348,3 +451,14 @@ void OverseerHandler::setRemoveEndpointWidgetFunction(std::function 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 b0cea92..cd0e03e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,6 +1,7 @@ #pragma once #include "global.h" +#include "settings.h" #include "contsessionclasses.h" //#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) //#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) @@ -16,11 +17,13 @@ struct BackEndpointVolumeCallbackInfo { NGuid caller; bool muted; float mainVolume; - size_t channels; + size_t channels; std::vector channelVolumes; bool updateName = false; }; +void setConfigDirToDefaults(); + class EndpointHandler { public: @@ -55,8 +58,8 @@ public: void setVolume(NGuid guid, int channel, int value); void setMute(NGuid guid, bool muted); - void setState(uint8_t state); - void setState(uint8_t state, uint64_t idx); + void setState(EndpointState state); + void setState(EndpointState state, uint64_t idx); float getPeakVolume(); /* sessions */ @@ -71,6 +74,13 @@ public: 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: @@ -97,11 +107,24 @@ class OverseerHandler { public: 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 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); @@ -116,25 +139,29 @@ public: void pushBackEndpointHandler(EndpointHandler* eph, Flows flow); uint64_t getPlaybackEndpointsCount(); uint64_t getCaptureEndpointsCount(); - void reloadEndpointHandlers(); - EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); + void createEndpointHandlers(); + EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); + void reportFinishedStateChange(); NGuid getGuid(); - /* - * void setSessionVolumeCallback(std::function changeSessionVolume); - * void setSessionVolume(float newValue, ); - */ + std::mutex handlersPlaybackMutex; + std::mutex handlersCaptureMutex; + void lockEndpoints(); + void unlockEndpoints(); private: 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 index b63631e..0f6caaf 100644 --- a/src/cont/contsessionclasses.cpp +++ b/src/cont/contsessionclasses.cpp @@ -5,7 +5,7 @@ SessionHandler::SessionHandler(EndpointHandler* eph, Session* session, size_t id 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(); @@ -85,3 +85,7 @@ void SessionHandler::reviseSessionShowing(SessionState state) { } } +SessionHandler::~SessionHandler() { + session->removeStateCallback(ssc); + ssc->Release(); +} diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h index 488d3ea..24c0ba7 100644 --- a/src/cont/contsessionclasses.h +++ b/src/cont/contsessionclasses.h @@ -9,10 +9,10 @@ class SessionStateCallback; struct SessionVolumeInfo { //SessionVolumeInfo(bool muted, float mainVolume); - bool muted; - float mainVolume; - NGuid caller; - bool isNameChanged = false; + bool muted; + float mainVolume; + NGuid caller; + std::atomic isNameChanged = false; //size_t channels; //std::vector channelVolumes; }; @@ -33,7 +33,7 @@ class SessionHandler { void setName(std::wstring newName); void reviseSessionShowing(SessionState state); SessionVolumeInfo* getVolumeInfo(); - + ~SessionHandler(); private: SessionVolumeInfo svi; EndpointHandler* eph; diff --git a/src/debug.h b/src/debug.h index 176825a..61855c1 100644 --- a/src/debug.h +++ b/src/debug.h @@ -2,6 +2,8 @@ #if defined (QT_DEBUG) || defined (DEBUG) || defined (_DEBUG) +#define PIPE_NAME "Mixerq-dev" + #ifdef INIT_FILELOG std::wstring_convert, wchar_t> converter; FILE* fileLog; @@ -56,7 +58,7 @@ std::bitset varToBitset(T info) { OutputDebugStringW(std::wstring(L"[DEBUG] (" + std::wstring(WFILE) + L":" + std::to_wstring(__LINE__) + L"): " + std::wstring(str) +L"\n").c_str()); \ } while (0) -#endif +#endif //_WIN32 #define log_to_file(fmt, cnt...) do { \ if(writable) fprintf_s(fileLog, fmt,##cnt); \ @@ -72,8 +74,9 @@ std::bitset varToBitset(T info) { #define print_as_binary(info) #define log_to_file(fmt, cnt...) #define initialize_file_log() false -#define close_file_log_buffer() -#endif +#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); */ diff --git a/src/global.h b/src/global.h index b8b9bcb..8799892 100644 --- a/src/global.h +++ b/src/global.h @@ -10,10 +10,17 @@ #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" @@ -30,8 +37,25 @@ #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), @@ -83,7 +107,11 @@ struct NGuid { /* }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 12355d7..a9148b9 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,18 +1,70 @@ #include "qtclasses.h" +#include "meterslider.h" + #define POLLING_RATE 2 +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()); + + //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; +} + template CustomWidgetEvent::CustomWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ this->payload = payload; } +/* + * 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; @@ -43,36 +95,45 @@ void MeterSlider::paintEvent(QPaintEvent *event) { * //sliderComplex.subControls = QStyle::SC_SliderGroove; * if (this->tickPosition() != NoTicks) sliderComplex.subControls |= QStyle::SC_SliderTickmarks; * QStylePainter p(this); - * p.drawComplexControl(QStyle::CC_Slider, sliderComplex); */ + //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; - * } - */ + + 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); /* @@ -87,40 +148,43 @@ void MeterSlider::paintEvent(QPaintEvent *event) { * //p.drawComplexControl(QStyle::CC_Slider, opt); */ //QSlider::paintEvent(event); - 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); + //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) { @@ -143,6 +207,36 @@ void ExtendedCheckBox::customEvent(QEvent* ev) { 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(); @@ -170,22 +264,22 @@ QRect MainWindow::setSizePosition(QScreen* screen, int width, int height) { 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), ary1, width, height); + return QRect((arx2 - width + 1), ary1, width, height); break; case SpawnPos::DOWN | SpawnPos::LEFT: this->addToolBar(Qt::TopToolBarArea, mainMenuBar); - return QRect(arx1, (ary2-height), width, height); + return QRect(arx1, (ary2 - height + 1), width, height); break; case SpawnPos::DOWN | SpawnPos::RIGHT: this->addToolBar(Qt::TopToolBarArea, mainMenuBar); - return QRect((arx2 - width), (ary2-height), width, height); + return QRect((arx2 - width + 1), (ary2 - height + 1), width, height); break; default: this->addToolBar(Qt::BottomToolBarArea, mainMenuBar); @@ -195,14 +289,24 @@ QRect MainWindow::setSizePosition(QScreen* screen, int width, int height) { } } -void MainWindow::compose() { - //todo: invalidate layout when adding sessions with window open +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 = this->getCurrentScreen(); + screen = StylingHelper::getCurrentScreen(); log_debugcpp("Screen: " + screen->model().toStdString() + " " + screen->name().toStdString()); QRect screenRes = screen->geometry(); @@ -210,19 +314,22 @@ void MainWindow::compose() { 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)); - uint64_t windowWidth = (uint64_t)std::abs(screenRes.width()) * this->widthRatio; - uint64_t screenHeight = (uint64_t)std::abs(screenRes.height()); + 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->setAttribute(Qt::WA_DontShowOnScreen, true); - this->show(); - this->widget->layout()->update(); - this->hide(); - this->setAttribute(Qt::WA_DontShowOnScreen, false); - + this->createLayout(new QGridLayout()); for (auto *epw : ews) { if (!epw) continue; - epw->calculateSize(windowWidth, screenHeight); + 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 = @@ -233,30 +340,33 @@ void MainWindow::compose() { /* * Calculating window height */ - uint64_t windowHeight = 0; + 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; - windowHeight += bottom; + windowHeight += top * 3; log_debugcpp("windowHeight loop: " + std::to_string(windowHeight)); } - windowHeight += mainMenuBar->height(); + windowHeight += mainMenuBar->sizeHint().height(); log_debugcpp("windowHeight final value: " + std::to_string(windowHeight)); - //Undoing scrolling - scrollArea->verticalScrollBar()->setValue(0); - - /* - * Establishing initial window size and position - */ - setGeometry(setSizePosition(screen, windowWidth, 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() { - //todo: Using cursor pos as screen detector. Flawed. + //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())); @@ -283,8 +393,11 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) widgetLayout->getContentsMargins(&left, &top, &right, &bottom); widgetLayout->setContentsMargins(0, top, 0, bottom); - muteButton = new QCheckBox(this); + 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*/); @@ -294,8 +407,8 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) mainSlider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); //mainSlider->setMinimumWidth(120 /*1/16 1080p*/); mainSlider->setFocusPolicy(Qt::StrongFocus); - mainSlider->setTickPosition(QSlider::TicksBothSides); - mainSlider->setTickInterval(5); + //mainSlider->setTickPosition(QSlider::TicksBothSides); + //mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); @@ -327,14 +440,18 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) 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) + 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)); @@ -352,7 +469,7 @@ 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->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*/); @@ -366,7 +483,9 @@ void SessionWidget::calculateSize(uint64_t width, uint64_t height) { 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("\tSpacer Minimum width: %d \n\n", widthSpacer->minimumSize().width()); + 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() { @@ -398,14 +517,14 @@ ChannelWidget::ChannelWidget(uint32_t channelCount, EndpointHandler* eph, QWidge widgetLayout->getContentsMargins(&left, &top, &right, &bottom); widgetLayout->setContentsMargins(0, top, 0, bottom); -/* - * Channel sliders setup - */ + /* + * Channel sliders setup + */ //uint32_t epChannelCount = eph->getChannelCount(); for(uint64_t channel = 0, col = 0, row = 0; channel < channelCount && channelCount > 1; channel++){ - QSlider* tmp = new QSlider(Qt::Horizontal); + MeterSlider* tmp = new MeterSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); - tmp->setTickInterval(5); + //tmp->setTickInterval(5); tmp->setSingleStep(1); tmp->setRange(0,100); @@ -413,8 +532,10 @@ ChannelWidget::ChannelWidget(uint32_t channelCount, EndpointHandler* eph, QWidge tmp->setValue((int) volume); tmpLb->setText(QString::number(volume)); //tmpLb->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - tmp->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - tmpLb->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + //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); @@ -431,16 +552,6 @@ ChannelWidget::ChannelWidget(uint32_t channelCount, EndpointHandler* eph, QWidge this->setLayout(widgetLayout); } -/* - * QSize ChannelWidget::minimumSizeHint() const { - * return minimum; - * } - * - * void ChannelWidget::setMinimum(QSize minimum) { - * this->minimum = minimum; - * } - */ - void ChannelWidget::updateChannel(int channel) { this->channelSliders.at(channel)->blockSignals(true); this->channelSliders.at(channel)->setValue((int)((eph->getCallbackInfo()->channelVolumes[channel] + roundingFactor) * 100)); @@ -458,11 +569,12 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i this->idx = idx; this->eph = eph; + eph->createSessionHandlers(); //todo: sussy - this->eph->setState(EndpointState::ENDPOINT_ACTIVE, idx); + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - //setAttribute(Qt::WA_TranslucentBackground); 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?"); } @@ -497,8 +609,8 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i //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->setTickPosition(QSlider::TicksBothSides); + //mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); @@ -531,8 +643,13 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i uint32_t epChannelCount = eph->getChannelCount(); if(epChannelCount > 1) { cw = new ChannelWidget(epChannelCount, eph, nullptr); - //cw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + 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); + } } /* @@ -547,6 +664,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i 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++); \ @@ -603,7 +721,6 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i 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)); @@ -614,21 +731,32 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i }); 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); @@ -638,6 +766,7 @@ void EndpointWidget::removeSessionWidget(CustomWidgetEvent* ev) sessionWidgets.at(i) = nullptr; //row--; ev->payload->setFrontIndex(INT_MAX); + this->setUpdatesEnabled(true); //this->sessionWidgetsUpdateTimer->start(); return; } @@ -653,11 +782,16 @@ void EndpointWidget::customEvent(QEvent* 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) { @@ -667,12 +801,18 @@ void MainWindow::customEvent(QEvent* 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){ +void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev) { uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->widgetLayout->removeWidget(ews.at(i)); @@ -680,14 +820,17 @@ void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ //delete ews.at(index); delete ews.at(i); ews.at(i) = nullptr; - this->ewsUpdateTimer->start(); + //TODO: is a flattener really necessary? + //this->ewsUpdateTimer->start(); return; } -void MainWindow::addEndpointWidget(CustomWidgetEvent* ev){ - EndpointWidget* epw = new EndpointWidget(ev->payload, widget, this->ews.size()); +void MainWindow::addEndpointWidget(CustomWidgetEvent* ev) { + EndpointWidget* epw = new EndpointWidget(ev->payload, containerWidget, this->ews.size()); //epw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - this->widgetLayout->addWidget(epw); + epw->setParent(this); + if(this->widgetLayout) + this->widgetLayout->addWidget(epw); ews.push_back(epw); return; } @@ -724,14 +867,27 @@ void MainWindow::reorderEndpointWidgetCollection() { 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: {Width: %u Height: %u}\n", width, height); - this->mainLabel->setMaximumWidth((int)(width * 0.50) /* 1080p 120%*/); - this->mainLabel->setMinimumWidth((int)(width * 0.50) /* 1080p 120%*/); + 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(QWIDGETSIZE_MAX, (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()); } @@ -782,6 +938,15 @@ void EndpointWidget::updateMainVolume(int newValue){ * } */ +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; } @@ -792,9 +957,9 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ * } */ -void EndpointWidget::setIndex(uint64_t idx){ +void EndpointWidget::setIndex(uint64_t idx) { this->idx = idx; - this->eph->setState(EndpointState::ENDPOINT_ACTIVE, this->idx); + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, this->idx); } uint64_t EndpointWidget::getIndex(){ @@ -808,34 +973,100 @@ std::map EndpointWidget::getDefaultRolesWidgets() { HeaderWidget::HeaderWidget(QWidget *parent) : QWidget(parent) { widgetLayout = new QGridLayout(this); - QString text = "&" STRING_ABOUT; - about = new QPushButton(text, 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_STARTUP; - startup = new QPushButton(text, this); - - widgetLayout->addWidget(openCP , 0, 0); - widgetLayout->addWidget(startup, 0, 1); + + 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); + //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-color: rgba(255,182,193,90%);"); + //setAttribute(Qt::WA_TranslucentBackground); + //setStyleSheet("background: transparent; "); + //setStyleSheet("background-color: rgba(255,182,193);"); setWindowTitle(STRING_TITLE); - connect(qApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state){ - if(state == Qt::ApplicationState::ApplicationInactive) hide(); -}); + /* + * 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 */ @@ -846,11 +1077,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { 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); - widget = new QWidget(); + recentlyClosedTimer = new QTimer(this); + containerWidget = new QWidget(); + //widget->setContentsMargins(0, 0, 0, 0); widgetLayout = new QGridLayout(); trayIcon = new QSystemTrayIcon(); trayIconMenu = new QMenu(); @@ -860,20 +1095,29 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { 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); + //widget->setLayout(widgetLayout); /* * Scroll bar code */ scrollArea = new QScrollArea(this); - scrollArea->setWidget(widget); + //widget->setAttribute(Qt::WA_TranslucentBackground); + scrollArea->setWidget(containerWidget); scrollArea->setWidgetResizable(true); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - //scrollArea->verticalScrollBar()->setSingleStep(1); + scrollArea->setContentsMargins(QMargins(0, 0, 0, 0)); - scrollArea->setStyleSheet("QScrollBar:vertical { width: 4px; }"); - //scrollArea->setMinimumWidth(500); + //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); /* @@ -884,10 +1128,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { mainMenuBar->addWidget(hw); mainMenuBar->setMovable(false); this->addToolBar(Qt::BottomToolBarArea, mainMenuBar); - + + /* + * Create initial endpoint widgets batch + */ reloadEndpointWidgets(); - //scrollArea->setMinimumWidth(ews.at(0)->minimumWidth()); - log_debugcpp(std::to_string(scrollArea->minimumWidth())); /* * Tray Icon code @@ -906,37 +1151,155 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { 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->setChangeFrontDefaultsFunction([this](Roles role, std::wstring endpointId) { - //Sigh... I hope to get this right when librole is done... - EndpointWidget *newDef = nullptr, *oldDef = nullptr; - for (uint64_t i = 0; i < ews.size(); i++) { - auto epw = this->ews.at(i); - if (!epw) continue; - if (epw->getEndpointHandler()->getId() == endpointId) { - newDef = epw; - continue; } - if (epw->getEndpointHandler()->getRoles() & role) { - oldDef = epw; - continue; } - } + 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); + }); - //debug if (role != Roles::ROLE_COMMUNICATIONS) return; - if (oldDef && newDef) { - this->ews.at(oldDef->getIndex()) = nullptr; - this->ews.at(newDef->getIndex()) = nullptr; + 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); - newDef->getEndpointHandler()->assignRoles(role); - QCoreApplication::instance()->postEvent(newDef->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + 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; - QCoreApplication::instance()->postEvent(newDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + 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); @@ -950,9 +1313,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { oldDef->getDefaultRolesWidgets().at(role)->blockSignals(true); uint8_t oldDefRoles = oldDef->getEndpointHandler()->getRoles(); - if (oldDefRoles == Roles::ROLE_ALL) QCoreApplication::instance()->postEvent(oldDef->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + 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()->postEvent(oldDef->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + 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) && @@ -973,17 +1340,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { } log_debugcpp("oldDef new idx: " + std::to_string(oldDef->getIndex())); oldDef->getDefaultRolesWidgets().at(role)->blockSignals(false); - } - }); - - 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)); - }); - + } + QCoreApplication::instance()->sendEvent + (this, new QEvent((QEvent::Type)CustomQEvent::RecomposeMainWindow)); } void MainWindow::closeEvent(QCloseEvent *event) { @@ -1001,9 +1360,12 @@ void MainWindow::closeEvent(QCloseEvent *event) { void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: - this->compose(); - this->showNormal(); - this->activateWindow(); + if (!this->isVisible() && !recentlyClosed) { + log_to_file("Recently Closed: %d \n", recentlyClosed); + this->compose(false); + this->showNormal(); + this->activateWindow(); + } break; default: break; @@ -1013,31 +1375,27 @@ void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { void MainWindow::reloadEndpointWidgets() { size_t i = 0; ews.resize(2); - //widgetLayout->addItem(&lastRowSpacer, i++, 0); - //todo: -log flag - //std::wofstream log("log.txt"); + + 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), widget); + 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; widgetLayout->addWidget(epw, 0, 0); epw->setIndex(0); } + { ews[0] = epw; epw->setIndex(0); } else if (epw->getEndpointHandler()->getRoles() & Roles::ROLE_COMMUNICATIONS) - { ews[1] = epw; widgetLayout->addWidget(epw, 1, 0); epw->setIndex(1); } - else { - epw->setIndex(epwIndex++); - //alfinal estoes solopara inicializarlmao - ews.push_back(epw); - widgetLayout->addWidget(epw, i, 0); } + { ews[1] = epw; epw->setIndex(1); } + else + { ews.push_back(epw); epw->setIndex(ews.size() - 1); } } } - //todo:: tas aqui tirao, no me gustas y probablemente yo a ti tampoco - //seguramente falle al querer rematar esto con redimensionar la ventana sólo - //con los default endpoints en vista + 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 c4eb958..1b2658b 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -1,52 +1,10 @@ #pragma once -//#ifndef MAINWINDOW_H -//#define MAINWINDOW_H - -#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" +#include "qtcommon.h" #include "contclasses.h" +#include "settings.h" + +class MeterSlider; enum SpawnPos { LEFT = (1 << 1), @@ -61,7 +19,14 @@ enum CustomQEvent { EndpointDefaultChange = 1003, SessionWidgetCreated = 1004, SessionWidgetObsolete = 1005, - RecomposeMainWindow = 1006 + RecomposeMainWindow = 1006, + EndpointRoleChange = 1007 +}; + +class DarkModeEventFilter : public QAbstractNativeEventFilter { + +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override; }; template @@ -73,30 +38,23 @@ public: }; //Q_DECLARE_METATYPE(EndpointWidgetEvent) -class MeterSlider : public QSlider { - Q_OBJECT -private: - float peakValue; -protected: - void paintEvent(QPaintEvent *event) override; -public: - void setPeakValue(float peakValue); - using QSlider::QSlider; -}; - 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 @@ -115,7 +73,7 @@ private: MeterSlider *mainSlider = nullptr; uint64_t idx; QHBoxLayout *widgetLayout = nullptr; - QCheckBox *muteButton = nullptr; + ExtendedCheckBox *muteButton = nullptr; SessionHandler* sh; QTimer* volumePoller = nullptr; QSpacerItem* widthSpacer; @@ -148,10 +106,10 @@ 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); @@ -164,7 +122,10 @@ public: //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); @@ -193,7 +154,7 @@ private: size_t defaultRolesVectorSize = 4; QTimer* timer = nullptr; uint64_t idx; - ChannelWidget* cw; + ChannelWidget* cw = nullptr; std::vector sessionWidgets; QSize minimum; //std::vector *ephs; @@ -212,10 +173,11 @@ public: private: QGridLayout *widgetLayout; - QPushButton *about; + //QPushButton *about; #ifdef WIN32 QPushButton *openCP; - QPushButton *startup; + QCheckBox *startup; + QCheckBox *channels; #endif }; @@ -226,14 +188,16 @@ class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr); void reloadEndpointWidgets(); - void compose(); + 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); @@ -244,8 +208,14 @@ private slots: private: //std::vector *ephs; + bool eventFilter(QObject *object, QEvent *event) override; + void flushRoleChanges(); + void changeFrontDefaults(Roles role, EndpointWidget* newDef, EndpointWidget* oldDef); + std::vector ews; - QWidget *widget; + std::deque> roleBucketList; + std::mutex roleBucketMutex; + QWidget *containerWidget; QGridLayout *widgetLayout; QSystemTrayIcon *trayIcon; @@ -255,13 +225,20 @@ private: 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); @@ -269,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 1d421cc..d264d01 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -1,25 +1,22 @@ -#include -#include - -#include -#include -#include -#include -#include -#include //#include "contclasses.h" #define INIT_FILELOG +#include "qtcommon.h" #include "qtclasses.h" -#include "global.h" +#include "qtvisuals.h" +#include "settings.h" -OverseerHandler *osh = nullptr; - -QApplication* createApplication(int &argc, char *argv[]) -{ +OverseerHandler *osh = nullptr; +ini::UserSettings *set = nullptr; + +bool startupRun = false; +bool onStartup = false; +char* userSettingsPath = nullptr; + +QApplication* createApplication(int &argc, char *argv[]) { return new QApplication(argc, argv); } -bool isSingleInstanceRunning(QString appName) { +bool isInstanceRunning(QString appName) { QLocalSocket socket; socket.connectToServer(appName); bool isOpen = socket.isOpen(); @@ -27,7 +24,7 @@ bool isSingleInstanceRunning(QString appName) { return isOpen; } -QLocalServer* startSingleInstanceServer(QString appName) { +QLocalServer* startInstanceServer(QString appName) { QLocalServer* server = new QLocalServer; server->setSocketOptions(QLocalServer::WorldAccessOption); server->listen(appName); @@ -38,6 +35,36 @@ 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(); @@ -47,35 +74,66 @@ void closeDebugFileLog() { int main (int argc, char* argv[]) { /* - * QStringList styles = QStyleFactory::keys(); - * for(QString a : styles) { - * log_debugcpp(a.toStdString()); - * } + * Debug: logging report file */ - //QApplication::setStyle("Fusion"); - //Check if running - //https://stackoverflow.com/questions/48060989/qt-show-application-if-currently-running initialize_file_log(); atexit(closeDebugFileLog); - //std::set_terminate(closeDebugFileLog2); - if (!isSingleInstanceRunning("Mixer")) - startSingleInstanceServer("Mixer"); + + 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); - osh = new OverseerHandler(); + /* + * 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->reloadEndpointHandlers(); + osh->createEndpointHandlers(); log_debugcpp("Reloaded endpoint handlers"); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); - + 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); 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"; +}; + +}