From 826373aee40a23d5bf1fb6ccc428a2b6c7e27df0 Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 11 Feb 2023 03:12:45 +0100 Subject: [PATCH 01/57] no copies objetos, anda --- src/back/backlasses.cpp | 26 ++++++++++++++------------ src/back/backlasses.h | 4 ++-- src/cont/contclasses.cpp | 4 ++-- src/cont/contclasses.h | 2 +- src/qtestmain.cpp | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 61e3562..6545136 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -25,12 +25,13 @@ void Endpoint::setVolume(float volume) { if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; } -//Endpoint::~Endpoint(){ -// free(friendlyName); -// properties->Release(); -// endpointVolume->Release(); -// endpoint->Release(); -//} +Endpoint::~Endpoint(){ + log_debugcpp("cum"); + free(friendlyName); + properties->Release(); + endpointVolume->Release(); + endpoint->Release(); +} void Overseer::initCOMLibrary(){ @@ -90,12 +91,13 @@ std::vector Overseer::getPlaybackEndpoints() { return playbackDevices; } -//Overseer::~Overseer(){ - // deviceEnumerator->Release(); - // for(unsigned long long i = 0; i < playbackDevices.size(); i++){ - //delete(playbackDevices.at(i)); - //} - //} +Overseer::~Overseer(){ + log_debugcpp("cum"); + deviceEnumerator->Release(); + for(unsigned long long i = 0; i < playbackDevices.size(); i++){ + delete(playbackDevices.at(i)); + } +} //int Overseer::getCaptureEndpoints(std::vector *captureEndpoints); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 90bc88b..dc4287a 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -25,7 +25,7 @@ class Endpoint { void setVolume(float volume); float getVolume(); LPWSTR getName(); - //~Endpoint(); + ~Endpoint(); private: IMMDevice* endpoint; @@ -46,7 +46,7 @@ class Overseer { //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); //int getCaptureEndpoints(std::vector *captureEndpoints); //IMMDeviceEnumerator** setOrigin(); - //~Overseer(); + ~Overseer(); private: unsigned int numPlaybackEndpoints; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 30b2688..8b5b752 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -19,8 +19,8 @@ float EndpointHandler::getVolume(){ return ept->getVolume(); } -Overseer OverseerHandler::getOverseer(){ - return os; +Overseer* OverseerHandler::getOverseer(){ + return &os; } OverseerHandler::OverseerHandler(QObject *parent) : QObject(parent) { diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 647ca1d..178a756 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -34,7 +34,7 @@ public: OverseerHandler(QObject *parent = nullptr); void setEndpointHandlers(std::vector *ephs); std::vector* getEndpointHandlers(); - static Overseer getOverseer(); + static Overseer* getOverseer(); private: static Overseer os; diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 610f0b4..1305a69 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -24,7 +24,7 @@ QApplication* createApplication(int &argc, char *argv[]) int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); //INIT CONT - std::vector epts = OverseerHandler::getOverseer().getPlaybackEndpoints(); + std::vector epts = OverseerHandler::getOverseer()->getPlaybackEndpoints(); std::vector* ephs = new std::vector; for(unsigned int i = 0; i < epts.size(); i++){ EndpointHandler *eph = new EndpointHandler(epts.at(i)); From b006896ccb065ec9aaa1e12a053029956f3b7efa Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 15 Feb 2023 10:17:59 +0100 Subject: [PATCH 02/57] debug.h y endpointwidget --- qtest.pro | 2 +- src/back/backlasses.cpp | 14 ++++++++ src/back/backlasses.h | 1 + src/cont/contclasses.h | 1 - src/debug.h | 10 ++++++ src/global.h | 8 +++-- src/qt/qtclasses.cpp | 79 +++++++++++++++++++++++++++++++---------- src/qt/qtclasses.h | 33 ++++++++++++++--- src/qtestmain.cpp | 3 -- 9 files changed, 121 insertions(+), 30 deletions(-) create mode 100644 src/debug.h diff --git a/qtest.pro b/qtest.pro index 7f7dbb8..abfbb93 100644 --- a/qtest.pro +++ b/qtest.pro @@ -4,5 +4,5 @@ INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp -HEADERS += qtclasses.h backlasses.h contclasses.h global.h +HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h #DESTDIR += "build" diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 6545136..933520f 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -21,6 +21,20 @@ float Endpoint::getVolume(){ return volume; } +/* + * float Endpoint::getLeftChannelVolume(){ + * float volume; + * if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + * return volume; + * } + * + * float Endpoint::getRightChannelVolume(){ + * float volume; + * if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + * return volume; + * } + */ + void Endpoint::setVolume(float volume) { if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index dc4287a..3a27869 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,6 +1,7 @@ #pragma once #define WIN32_LEAN_AND_MEAN +#include "debug.h" #include "global.h" #include #include diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 178a756..6a3d28e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,6 +1,5 @@ #pragma once #include -#include "global.h" #include "backlasses.h" diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..f91232b --- /dev/null +++ b/src/debug.h @@ -0,0 +1,10 @@ +#pragma once + +#if defined (QT_DEBUG) || defined (DEBUG) || defined (_DEBUG) + +#define log_debugcpp(str) do { \ + std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ + } while (0) +#else +#define log_debugcpp(str) +#endif diff --git a/src/global.h b/src/global.h index c44b7e5..fff36de 100644 --- a/src/global.h +++ b/src/global.h @@ -1,4 +1,6 @@ #pragma once -#define log_debugcpp(str) do { \ - std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ - } while (0) + +//INIT BACK +class OverseerHandler; +extern OverseerHandler *osh; + diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 38ae3f2..ba25884 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,5 +1,50 @@ #include "qtclasses.h" + +EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + layout = new QGridLayout(); + this->setLayout(layout); + log_debugcpp("olaW"); + if (parent == nullptr) { log_debugcpp("owo?"); } + + mainLabel = new QLabel(eph->getName()); + leftChannelLabel = new QLabel("88"); + rightChannelLabel = new QLabel("77"); + mainSlider = new QSlider(Qt::Horizontal); + leftChannelSlider = new QSlider(Qt::Horizontal); + rightChannelSlider = new QSlider(Qt::Horizontal); + + mainSlider->setFocusPolicy(Qt::StrongFocus); + mainSlider->setTickPosition(QSlider::TicksBothSides); + + mainSlider->setTickInterval(5); + mainSlider->setSingleStep(1); + mainSlider->setRange(0,100); + leftChannelSlider->setTickInterval(5); + leftChannelSlider->setSingleStep(1); + leftChannelSlider->setRange(0,100); + rightChannelSlider->setTickInterval(5); + rightChannelSlider->setSingleStep(1); + rightChannelSlider->setRange(0,100); + + float volume = eph->getVolume() * 100; + mainSlider->setValue((int)volume); + log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); + + layout->addWidget(mainLabel, 0, 0); + layout->addWidget(mainSlider, 0, 1); + layout->addWidget(leftChannelSlider, 1, 0); + layout->addWidget(leftChannelLabel, 2, 0); + layout->addWidget(rightChannelSlider, 1, 1); + layout->addWidget(rightChannelLabel, 2, 1); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); + + connect(mainSlider, &QSlider::valueChanged, eph, &EndpointHandler::setValue); + log_debugcpp("ENDPOINT_WIDGETED"); +} + + MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : QMainWindow(parent) { // setWindowState(Qt::WindowFullScreen); // setCentralWidget(centralWidget); @@ -12,27 +57,25 @@ MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : Q setWindowTitle("slidea resbala nu c"); - setEndpointHandlers(ephs); - for (unsigned int i = 0; i < this->ephs->size(); i++){ - QLabel *pintas = new QLabel(ephs->at(i)->getName()); - QSlider *teSlider = new QSlider(Qt::Horizontal); - teSlider->setFocusPolicy(Qt::StrongFocus); - teSlider->setTickPosition(QSlider::TicksBothSides); - teSlider->setTickInterval(5); - teSlider->setSingleStep(1); - teSlider->setRange(0,100); - float volume = ephs->at(i)->getVolume() * 100; - teSlider->setValue((int)volume); - log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); - layout->addWidget(pintas, i, 0); - layout->addWidget(teSlider, i, 1); - connect(teSlider, &QSlider::valueChanged, ephs->at(i), &EndpointHandler::setValue); + /*s + * setEndpointHandlers(ephs); + */ + unsigned int i = 0; + for (; i < ephs->size(); i++) { + log_debugcpp("EPWidget creation"); + EndpointWidget *epw = new EndpointWidget(ephs->at(i), widget); + ews.push_back(epw); + layout->addWidget(epw, i, 0); } + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } -void MainWindow::setEndpointHandlers(std::vector *ephs){ - this->ephs = ephs; -} +/* + * void MainWindow::setEndpointHandlers(std::vector *ephs){ + * this->ephs = ephs; + */ + + /* * void MainWindow::setPlotButton() { diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6ef82b0..6294a4f 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -8,23 +8,48 @@ #include #include #include -//#include "global.h" #include "contclasses.h" //#include //#include +class EndpointWidget : public QWidget { + Q_OBJECT + +public: + EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr); + //void populateEndpointWidget(EndpointHandler *eph); + //void setEndpointHandlers(std::vector *ephs); + +private: + QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; + QSlider *mainSlider = nullptr; + QSlider *leftChannelSlider = nullptr; + QSlider *rightChannelSlider = nullptr; + QGridLayout *layout = nullptr; + //std::vector *ephs; + //std::vector *sliders; + + //public slots: + // void setEndpointHandlers(std::vector *ephs); + + //signals: + //void valueChanged(int value); + +}; + + class MainWindow : public QMainWindow { Q_OBJECT //QWidget *centralWidget; public: MainWindow(std::vector *ephs, QWidget *parent = nullptr); - void setEndpointHandlers(std::vector *ephs); + //void setEndpointHandlers(std::vector *ephs); private: - std::vector *ephs; - std::vector *sliders; + //std::vector *ephs; + std::vector ews; QWidget *widget; QGridLayout *layout; //QLabel *pintas; diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 1305a69..883d7e7 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -9,10 +9,7 @@ #include #include "qtclasses.h" -//TODO david #include "backlasses.h" - -//INIT BACK OverseerHandler *osh = new OverseerHandler(); QApplication* createApplication(int &argc, char *argv[]) From eaa935fb39e7ffdd98c972eb05d3b4da1c958745 Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 15 Feb 2023 12:40:43 +0100 Subject: [PATCH 03/57] barritas de canal que funcionan weeeee --- src/back/backlasses.cpp | 22 ++++++++++++++++------ src/back/backlasses.h | 6 ++++-- src/cont/contclasses.cpp | 14 +++++++++----- src/cont/contclasses.h | 6 +++--- src/global.h | 5 +++++ src/qt/qtclasses.cpp | 16 ++++++++++++++-- src/qt/qtclasses.h | 1 + 7 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 933520f..da50727 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -15,28 +15,38 @@ LPWSTR Endpoint::getName(){ return friendlyName; } -float Endpoint::getVolume(){ +float Endpoint::getVolume(int channel){ float volume; - if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + if (channel == ENDPOINT_MASTER_VOLUME) { + if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + } else { + if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { log_debugcpp("si");} + } return volume; } + /* * float Endpoint::getLeftChannelVolume(){ * float volume; - * if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + * if(FAILED(endpointVolume-> GetChannelVolumeLevelScalar(0, &volume)) { log_debugcpp("si"); } ); * return volume; * } * * float Endpoint::getRightChannelVolume(){ * float volume; - * if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + * if(FAILED(endpointVolume-> GetChannelVolumeLevelScalar(1, &volume)) { log_debugcpp("si");} * return volume; * } */ -void Endpoint::setVolume(float volume) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; + +void Endpoint::setVolume(int channel, float volume) { + if (channel == ENDPOINT_MASTER_VOLUME) { + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; + } else { + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, NULL))) { log_debugcpp("si"); }; + } } Endpoint::~Endpoint(){ diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 3a27869..6b36fb6 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -23,8 +23,10 @@ class Endpoint { public: Endpoint(IMMDevice* endpoint); - void setVolume(float volume); - float getVolume(); + void setVolume(int channel, float volume); + /* float getLeftChannelVolume(); */ + /* float getRightChannelVolume(); */ + float getVolume(int channel); LPWSTR getName(); ~Endpoint(); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 8b5b752..e8ba796 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -6,17 +6,21 @@ EndpointHandler::EndpointHandler(Endpoint *ept, QObject *parent) : QObject(paren this->ept = ept; eptName = QString::fromStdWString(ept->getName()); } - -void EndpointHandler::setValue(int value){ - ept->setVolume((float)value / 100); +/* + * -1 for master volume + */ +void EndpointHandler::setValue(int channel, int value){ + if (channel == ENDPOINT_MASTER_VOLUME) + ept->setVolume(channel, (float)value / 100); + else ept->setVolume(channel, (float)value / 100); } QString EndpointHandler::getName(){ return eptName; } -float EndpointHandler::getVolume(){ - return ept->getVolume(); +float EndpointHandler::getVolume(int channel){ + return ept->getVolume(channel); } Overseer* OverseerHandler::getOverseer(){ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 6a3d28e..c496e0c 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -9,15 +9,15 @@ class EndpointHandler : public QObject { public: EndpointHandler(Endpoint *ept, QObject *parent = nullptr); QString getName(); - float getVolume(); - + float getVolume(int channel); + private: Endpoint *ept; QString eptName; //QSlider *slidy; public slots: - void setValue(int value); + void setValue(int channel, int value); //signals: diff --git a/src/global.h b/src/global.h index fff36de..db86521 100644 --- a/src/global.h +++ b/src/global.h @@ -1,6 +1,11 @@ #pragma once +//TODO enum capullo +#define ENDPOINT_MASTER_VOLUME -1 +#define ENDPOINT_LEFT_CHANNEL_VOLUME 0 +#define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 //INIT BACK + class OverseerHandler; extern OverseerHandler *osh; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index ba25884..43361ae 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -2,6 +2,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + this->eph = eph; layout = new QGridLayout(); this->setLayout(layout); log_debugcpp("olaW"); @@ -27,8 +28,14 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( rightChannelSlider->setSingleStep(1); rightChannelSlider->setRange(0,100); - float volume = eph->getVolume() * 100; + float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; mainSlider->setValue((int)volume); + volume = eph->getVolume(ENDPOINT_LEFT_CHANNEL_VOLUME) * 100; + leftChannelSlider->setValue((int)volume); + leftChannelLabel->setText(QString::number(volume)); + volume = eph->getVolume(ENDPOINT_RIGHT_CHANNEL_VOLUME) * 100; + rightChannelSlider->setValue((int)volume); + rightChannelLabel->setText(QString::number(volume)); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); layout->addWidget(mainLabel, 0, 0); @@ -40,7 +47,12 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); - connect(mainSlider, &QSlider::valueChanged, eph, &EndpointHandler::setValue); + /* + * connect(mainSlider, &QSlider::valueChanged, eph, &EndpointHandler::setValue); + */ + connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); + connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); + connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); log_debugcpp("ENDPOINT_WIDGETED"); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6294a4f..1f7beee 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -22,6 +22,7 @@ public: //void setEndpointHandlers(std::vector *ephs); private: + EndpointHandler* eph; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; QSlider *leftChannelSlider = nullptr; From e42c2dd6c9c8021c53c4bcecfa1f039c83c4bf69 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 16 Feb 2023 20:34:54 +0100 Subject: [PATCH 04/57] mute city boton momento --- src/back/backlasses.cpp | 18 ++++++++++++++++++ src/back/backlasses.h | 4 +++- src/cont/contclasses.cpp | 10 ++++++++++ src/cont/contclasses.h | 4 ++-- src/global.h | 4 ++++ src/qt/qtclasses.cpp | 12 +++++++++--- src/qt/qtclasses.h | 5 ++++- 7 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index da50727..acd4f3a 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -26,6 +26,15 @@ float Endpoint::getVolume(int channel){ } +bool Endpoint::getMute(){ + BOOL mut; + if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } + log_debugcpp("back BOOL is " << mut); + bool mute = (bool)mut; + log_debugcpp("translate to bool " << mute); + return mute; +} + /* * float Endpoint::getLeftChannelVolume(){ * float volume; @@ -49,6 +58,15 @@ void Endpoint::setVolume(int channel, float volume) { } } +void Endpoint::setMute() { + log_debugcpp("bool mute arrives as " << mut); + BOOL mut; + if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } + log_debugcpp("translate to BOOL as " << mute); + if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), NULL))) { log_debugcpp("si"); }; +} + + Endpoint::~Endpoint(){ log_debugcpp("cum"); free(friendlyName); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 6b36fb6..ebfe0e5 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -26,7 +26,9 @@ class Endpoint { void setVolume(int channel, float volume); /* float getLeftChannelVolume(); */ /* float getRightChannelVolume(); */ - float getVolume(int channel); + float getVolume(int channel); + void setMute(); + bool getMute(); LPWSTR getName(); ~Endpoint(); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index e8ba796..bd05f88 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -15,6 +15,12 @@ void EndpointHandler::setValue(int channel, int value){ else ept->setVolume(channel, (float)value / 100); } +void EndpointHandler::setMute(){ + //Qt momento, de ahi el param? + log_debugcpp("kinda handling the muting tbh"); + ept->setMute(); +} + QString EndpointHandler::getName(){ return eptName; } @@ -23,6 +29,10 @@ float EndpointHandler::getVolume(int channel){ return ept->getVolume(channel); } +bool EndpointHandler::getMute(){ + return ept->getMute(); +} + Overseer* OverseerHandler::getOverseer(){ return &os; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index c496e0c..c562b73 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -10,6 +10,7 @@ public: EndpointHandler(Endpoint *ept, QObject *parent = nullptr); QString getName(); float getVolume(int channel); + bool getMute(); private: Endpoint *ept; @@ -18,8 +19,7 @@ private: public slots: void setValue(int channel, int value); - - + void setMute(); //signals: diff --git a/src/global.h b/src/global.h index db86521..96d177b 100644 --- a/src/global.h +++ b/src/global.h @@ -4,8 +4,12 @@ #define ENDPOINT_MASTER_VOLUME -1 #define ENDPOINT_LEFT_CHANNEL_VOLUME 0 #define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 + +#define STRING_MUTE "Mute" +#define STRING_UNMUTE "Unmute" //INIT BACK + class OverseerHandler; extern OverseerHandler *osh; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 43361ae..0cac13d 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -8,6 +8,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( log_debugcpp("olaW"); if (parent == nullptr) { log_debugcpp("owo?"); } + muteButton = new QPushButton(); mainLabel = new QLabel(eph->getName()); leftChannelLabel = new QLabel("88"); rightChannelLabel = new QLabel("77"); @@ -15,9 +16,9 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( leftChannelSlider = new QSlider(Qt::Horizontal); rightChannelSlider = new QSlider(Qt::Horizontal); + muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); mainSlider->setFocusPolicy(Qt::StrongFocus); mainSlider->setTickPosition(QSlider::TicksBothSides); - mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); @@ -27,7 +28,8 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( rightChannelSlider->setTickInterval(5); rightChannelSlider->setSingleStep(1); rightChannelSlider->setRange(0,100); - + + muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; mainSlider->setValue((int)volume); volume = eph->getVolume(ENDPOINT_LEFT_CHANNEL_VOLUME) * 100; @@ -38,7 +40,10 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( rightChannelLabel->setText(QString::number(volume)); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); - layout->addWidget(mainLabel, 0, 0); + mainMuteLayout = new QGridLayout(); + layout->addLayout(mainMuteLayout, 0, 0); + mainMuteLayout->addWidget(mainLabel, 0, 0); + mainMuteLayout->addWidget(muteButton, 0, 1); layout->addWidget(mainSlider, 0, 1); layout->addWidget(leftChannelSlider, 1, 0); layout->addWidget(leftChannelLabel, 2, 0); @@ -53,6 +58,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); + connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); log_debugcpp("ENDPOINT_WIDGETED"); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 1f7beee..a02bff8 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "contclasses.h" //#include //#include @@ -20,14 +21,16 @@ public: EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); - + private: EndpointHandler* eph; + QPushButton *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; QSlider *leftChannelSlider = nullptr; QSlider *rightChannelSlider = nullptr; QGridLayout *layout = nullptr; + QGridLayout *mainMuteLayout = nullptr; //std::vector *ephs; //std::vector *sliders; From f84ddaef6cfc44ad640e2692595e020c6a1331cc Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 9 Aug 2023 15:55:25 +0200 Subject: [PATCH 05/57] failed attempt at redrawing --- src/back/backlasses.cpp | 58 ++++++++++++++++++++++++++++++++++++++++ src/back/backlasses.h | 20 ++++++++++++++ src/cont/contclasses.cpp | 43 ++++++++++++++++++++++++----- src/cont/contclasses.h | 21 ++++++++------- src/global.h | 1 - src/qt/qtclasses.cpp | 2 ++ src/qt/qtclasses.h | 12 ++++++--- src/qtestmain.cpp | 10 ++++--- 8 files changed, 142 insertions(+), 25 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index acd4f3a..9232afc 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,5 +1,50 @@ #include +ULONG EndpointCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioEndpointVolumeCallback) == riid) + { + AddRef(); + *ppvInterface = (IAudioEndpointVolumeCallback*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { + if (pNotify == NULL) return E_INVALIDARG; + + LPGUID guid = osh->getOverseer()->getGuid(); + if (pNotify->guidEventContext != *guid) { + osh->parseExternalEndpointCallback(this, pNotify); + } + return S_OK; +} + +/* EndpointCallback::~EndpointCallback(){ + * PAUDIO_VOLUME_NOTIFICATION_DATA->Release(); + * } */ + Endpoint::Endpoint(IMMDevice* ep){ this->endpoint = ep; if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { log_debugcpp("si"); }; @@ -66,6 +111,13 @@ void Endpoint::setMute() { if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), NULL))) { log_debugcpp("si"); }; } +void Endpoint::setCallback(EndpointCallback *epc){ + endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)*epc); +} + +void Endpoint::removeCallback(EndpointCallback *epc){ + endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)*epc); +} Endpoint::~Endpoint(){ log_debugcpp("cum"); @@ -87,6 +139,8 @@ void Overseer::initCOMLibrary(){ (void**)&deviceEnumerator)) ) { log_debugcpp("si"); }; + if(FAILED(CoCreateGuid(guid))) { log_debugcpp("guyyyyyy"); }; + } void Overseer::reloadEndpoints() { @@ -129,6 +183,10 @@ Overseer::Overseer(){ //int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); +LPGUID Overseer::getGuid() { + return guid; +} + std::vector Overseer::getPlaybackEndpoints() { return playbackDevices; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index ebfe0e5..7c28408 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -19,6 +19,22 @@ //#include #include +class EndpointCallback : public IAudioEndpointVolumeCallback { + + public: + EndpointCallback(); + + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA update); + ~EndpointCallback(); + + private: + ULONG ref; + PAUDIO_VOLUME_NOTIFICATION_DATA update; +}; + class Endpoint { public: @@ -30,6 +46,8 @@ class Endpoint { void setMute(); bool getMute(); LPWSTR getName(); + void setCallback(EndpointCallback *epc); + void removeCallback(EndpointCallback *epc); ~Endpoint(); private: @@ -46,6 +64,7 @@ class Overseer { Overseer(); std::vector getPlaybackEndpoints(); void reloadEndpoints(); + LPGUID getGuid(); //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); @@ -54,6 +73,7 @@ class Overseer { ~Overseer(); private: + LPGUID guid; unsigned int numPlaybackEndpoints; IMMDeviceEnumerator *deviceEnumerator; std::vector playbackDevices; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index bd05f88..e93de35 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -2,10 +2,13 @@ Overseer OverseerHandler::os; -EndpointHandler::EndpointHandler(Endpoint *ept, QObject *parent) : QObject(parent) { - this->ept = ept; - eptName = QString::fromStdWString(ept->getName()); +EndpointHandler::EndpointHandler(Endpoint *ep, EndpointCallback *epc, QObject *parent) : QObject(parent) { + this->ep = ep; + this->epc = epc; + epName = QString::fromStdWString(ept->getName()); + ep->setCallback(*epc); } + /* * -1 for master volume */ @@ -33,6 +36,13 @@ bool EndpointHandler::getMute(){ return ept->getMute(); } +EndpointHandler::~EndpointHandler() { + ep->removeCallback(*epc); + delete epc; + delete ep; +} + + Overseer* OverseerHandler::getOverseer(){ return &os; } @@ -41,10 +51,29 @@ OverseerHandler::OverseerHandler(QObject *parent) : QObject(parent) { } -std::vector* OverseerHandler::getEndpointHandlers(){ - return endpointHandlers; +std::vector* OverseerHandler::getEndpointWidgets(){ + return &endpointWidgets; } -void OverseerHandler::setEndpointHandlers(std::vector *ephs){ - this->endpointHandlers = ephs; +void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify){ + log_debugcpp("parsing in da ovasiar"); + for (uint64_t i = 0; i < endpointWidgets.size(); i++){ + if(endpointWidgets.at(i)->eph->epc == fEpc) { + endpointWidgets.at(i)->muteButton->setText(endpointWidgets.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + break; + } + } + + /* + * connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); + * connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); + * connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); + * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); + * log_debugcpp("ENDPOINT_WIDGETED"); + */ } + +void OverseerHandler::setEndpointWidgets(std::vector ews){ + this->endpointWidgets = ews; +} + diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index c562b73..f11f312 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,4 +1,5 @@ #pragma once + #include #include "backlasses.h" @@ -7,15 +8,18 @@ class EndpointHandler : public QObject { Q_OBJECT public: - EndpointHandler(Endpoint *ept, QObject *parent = nullptr); + EndpointHandler(Endpoint *ept, EndpointCallback *epc, QObject *parent = nullptr); + //TODO: get(); + Endpoint *ep; + EndpointCallback *epc; + QString epName; + QString getName(); float getVolume(int channel); bool getMute(); private: - Endpoint *ept; - QString eptName; - //QSlider *slidy; + //QSlider *slidy; public slots: void setValue(int channel, int value); @@ -31,18 +35,17 @@ class OverseerHandler : public QObject { public: OverseerHandler(QObject *parent = nullptr); - void setEndpointHandlers(std::vector *ephs); - std::vector* getEndpointHandlers(); + void setEndpointWidgets(std::vector ews); + std::vector* getEndpointWidgets(); + void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); static Overseer* getOverseer(); private: static Overseer os; - std::vector *endpointHandlers; + std::vector endpointWidgets; //QSlider *slidy; //public slots: //void setValue(int value); - - }; diff --git a/src/global.h b/src/global.h index 96d177b..977e0b8 100644 --- a/src/global.h +++ b/src/global.h @@ -9,7 +9,6 @@ #define STRING_UNMUTE "Unmute" //INIT BACK - class OverseerHandler; extern OverseerHandler *osh; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 0cac13d..70334c3 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -85,6 +85,7 @@ MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : Q ews.push_back(epw); layout->addWidget(epw, i, 0); } + osh->setEndpointWidgets(ews); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } @@ -112,3 +113,4 @@ MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : Q * ... */ + diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index a02bff8..e7e9d91 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -19,11 +19,9 @@ class EndpointWidget : public QWidget { public: EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr); - //void populateEndpointWidget(EndpointHandler *eph); - //void setEndpointHandlers(std::vector *ephs); - -private: + //TODO: get(); EndpointHandler* eph; + QPushButton *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; @@ -31,6 +29,11 @@ private: QSlider *rightChannelSlider = nullptr; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; + //void populateEndpointWidget(EndpointHandler *eph); + //void setEndpointHandlers(std::vector *ephs); + +private: + //std::vector *ephs; //std::vector *sliders; @@ -67,3 +70,4 @@ private: }; #endif + diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 883d7e7..d619db1 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -9,6 +9,7 @@ #include #include "qtclasses.h" +#include "global.h" OverseerHandler *osh = new OverseerHandler(); @@ -21,14 +22,14 @@ QApplication* createApplication(int &argc, char *argv[]) int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); //INIT CONT - std::vector epts = OverseerHandler::getOverseer()->getPlaybackEndpoints(); + std::vector eps = OverseerHandler::getOverseer()->getPlaybackEndpoints(); std::vector* ephs = new std::vector; - for(unsigned int i = 0; i < epts.size(); i++){ - EndpointHandler *eph = new EndpointHandler(epts.at(i)); + for(unsigned int i = 0; i < eps.size(); i++){ + EndpointCallback* epc = new EndpointCallback(); + EndpointHandler* eph = new EndpointHandler(eps.at(i), epc); ephs->push_back(eph); } - osh->setEndpointHandlers(ephs); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); MainWindow window = MainWindow(ephs); @@ -37,3 +38,4 @@ int main (int argc, char* argv[]) { window.show(); return app->exec(); } + From cf1829bd78c029bc1e4d4fc8eff16e5257c02c23 Mon Sep 17 00:00:00 2001 From: Phireh Date: Wed, 9 Aug 2023 20:00:18 +0200 Subject: [PATCH 06/57] Declare EndpointWidget before using it --- src/back/backlasses.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 7c28408..2b2ff74 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -19,6 +19,8 @@ //#include #include +class EndpointWidget; + class EndpointCallback : public IAudioEndpointVolumeCallback { public: From ad34a38f3847d06260344534457480b859416518 Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 9 Aug 2023 20:31:47 +0200 Subject: [PATCH 07/57] updated build files --- bueno.bat | 2 +- qtest.pro | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bueno.bat b/bueno.bat index cd190f0..67fbd6a 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,2 +1,2 @@ qmake -o build\Makefile .\qtest.pro -mingw32-make.exe -C .\build -f Makefile.Release +mingw32-make.exe -C .\build -f Makefile diff --git a/qtest.pro b/qtest.pro index abfbb93..39fad14 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,3 +1,6 @@ +QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 +QMAKE_LINKER += clang++ +QMAKE_LFLAGS += -v CONFIG += debug console QT += widgets INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" From bc82ec72ed08f3ef56e81a9bde5a4eec290b8fa3 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 10 Aug 2023 21:34:09 +0200 Subject: [PATCH 08/57] wip partial refactor --- src/back/backlasses.cpp | 27 +++++++++++++---- src/back/backlasses.h | 13 ++++++--- src/cont/contclasses.cpp | 63 ++++++++++++++++++++++++++++++---------- src/cont/contclasses.h | 35 ++++++++++++---------- src/qt/qtclasses.cpp | 15 ++++++++-- src/qt/qtclasses.h | 6 ++-- src/qtestmain.cpp | 10 ++----- 7 files changed, 115 insertions(+), 54 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 9232afc..1a48dd7 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,5 +1,9 @@ #include +EndpointCallback::EndpointCallback(Endpoint* ep){ + this.ep = ep; +} + ULONG EndpointCallback::AddRef(){ return InterlockedIncrement(&ref); } @@ -33,10 +37,11 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - - LPGUID guid = osh->getOverseer()->getGuid(); - if (pNotify->guidEventContext != *guid) { - osh->parseExternalEndpointCallback(this, pNotify); + AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; + LPGUID guid = osh->getOverseer()->getGuid(); + + if (eventData.guidEventContext != *guid) { + osh->parseExternalEndpointCallback(this, eventData); } return S_OK; } @@ -56,6 +61,15 @@ Endpoint::Endpoint(IMMDevice* ep){ friendlyName = pv.pwszVal; } +void Endpoint::setIndex(uint64_t idx){ + this.idx = idx; +} + +uint64_t Endpoint::getIndex(){ + return idx; +} + + LPWSTR Endpoint::getName(){ return friendlyName; } @@ -112,11 +126,11 @@ void Endpoint::setMute() { } void Endpoint::setCallback(EndpointCallback *epc){ - endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)*epc); + endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } void Endpoint::removeCallback(EndpointCallback *epc){ - endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)*epc); + endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } Endpoint::~Endpoint(){ @@ -160,6 +174,7 @@ void Overseer::reloadEndpoints() { IMMDevice *temp; if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; Endpoint *endpoint = new Endpoint(temp); + endpoint->setIndex(i); this->playbackDevices.push_back(endpoint); //TODO: le porblemx std::cout << "ola" << std::endl; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 2b2ff74..2da5724 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -5,6 +5,7 @@ #include "global.h" #include #include +#include #include #include @@ -19,12 +20,12 @@ //#include #include -class EndpointWidget; +//class EndpointWidget; class EndpointCallback : public IAudioEndpointVolumeCallback { public: - EndpointCallback(); + EndpointCallback(Endpoint* ep); ULONG AddRef(); ULONG Release(); @@ -33,7 +34,8 @@ class EndpointCallback : public IAudioEndpointVolumeCallback { ~EndpointCallback(); private: - ULONG ref; + ULONG ref = 1; + Endpoint* ep; PAUDIO_VOLUME_NOTIFICATION_DATA update; }; @@ -41,6 +43,8 @@ class Endpoint { public: Endpoint(IMMDevice* endpoint); + uint64_t getIndex(); + void setIndex(uint64_t idx); void setVolume(int channel, float volume); /* float getLeftChannelVolume(); */ /* float getRightChannelVolume(); */ @@ -56,7 +60,8 @@ class Endpoint { IMMDevice* endpoint; IAudioEndpointVolume *endpointVolume ; IPropertyStore *properties; - LPWSTR friendlyName; + std::wstring friendlyName; + uint64_t idx; // LPWSTR endpointID = NULL; }; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index e93de35..f5c2855 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,14 +1,25 @@ +#incluse "backlasses.h" #include "contclasses.h" Overseer OverseerHandler::os; -EndpointHandler::EndpointHandler(Endpoint *ep, EndpointCallback *epc, QObject *parent) : QObject(parent) { - this->ep = ep; - this->epc = epc; +EndpointHandler::EndpointHandler(uint64_t idx) { + std::vector endpoints = osh->getPlaybackEndpoints(); + this->ep = endpoints.at(idx); + epc = new EndpointCallback(ep); epName = QString::fromStdWString(ept->getName()); ep->setCallback(*epc); } +void EndpointHandler::setIndex(uint64_t idx){ + this.idx = idx; +} + +uint64_t EndpointHandler::getIndex(){ + return idx; +} + + /* * -1 for master volume */ @@ -42,24 +53,46 @@ EndpointHandler::~EndpointHandler() { delete ep; } - -Overseer* OverseerHandler::getOverseer(){ - return &os; +std::vector OverseerHandler::getPlaybackEndpoints() { + return os->getPlaybackEndpoints(); } -OverseerHandler::OverseerHandler(QObject *parent) : QObject(parent) { - +/* + * Overseer* OverseerHandler::getOverseer(){ + * return &os; + * } + */ + + +std::vector* OverseerHandler::getEndpointHandlers(){ + return &endpointHandlers; } -std::vector* OverseerHandler::getEndpointWidgets(){ - return &endpointWidgets; +uint64_t OverseerHandler::getPlaybackEndpointsCount(){ + return os->getplaybackEndpoints().size(); +} + +void OverseerHandler::reloadEndpointHandlers(){ + //std::vector* ephs = new std::vector; + + for(uint64_t i = 0; i < osh->getPlaybackEndpointsCount(); i++){ + if(i < osh->getPlaybackEndpointsCount().size() && + osh->getPlaybackEndpointsCount().at(i) != nullptr) + delete ephs.at(i); + EndpointHandler* eph = new EndpointHandler(i); + + if (i >= osh->getPlaybackEndpointsCount().size()) + ephs.push_back(eph); + else epsh.at(i) = eph; + } + //setEndpointHandlers(ephs); } void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify){ log_debugcpp("parsing in da ovasiar"); - for (uint64_t i = 0; i < endpointWidgets.size(); i++){ - if(endpointWidgets.at(i)->eph->epc == fEpc) { - endpointWidgets.at(i)->muteButton->setText(endpointWidgets.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + for (uint64_t i = 0; i < endpointHandlers.size(); i++){ + if(endpointHandlers.at(i)->eph->epc == fEpc) { + endpointHandlers.at(i)->muteButton->setText(endpointHandlers.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); break; } } @@ -73,7 +106,7 @@ void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUD */ } -void OverseerHandler::setEndpointWidgets(std::vector ews){ - this->endpointWidgets = ews; +void OverseerHandler::setEndpointHandlers(std::vector ews){ + this->endpointHandlers = ews; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index f11f312..ceeb17c 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,48 +1,51 @@ #pragma once -#include #include "backlasses.h" -class EndpointHandler : public QObject { - Q_OBJECT - +class EndpointHandler { + public: - EndpointHandler(Endpoint *ept, EndpointCallback *epc, QObject *parent = nullptr); + EndpointHandler(Endpoint *ept); //TODO: get(); Endpoint *ep; EndpointCallback *epc; QString epName; + + void setIndex(uint64_t idx); + void setVolume(int channel, float volume); + //todo qstrin????? idiota QString getName(); float getVolume(int channel); bool getMute(); - + + void setValue(int channel, int value); + void setMute(); private: + uint64_t idx; //QSlider *slidy; -public slots: - void setValue(int channel, int value); - void setMute(); //signals: }; -class OverseerHandler : public QObject { - Q_OBJECT +class OverseerHandler { public: - OverseerHandler(QObject *parent = nullptr); - void setEndpointWidgets(std::vector ews); - std::vector* getEndpointWidgets(); + //OverseerHandler(); + void setEndpointHandlers(std::vector ews); + std::vector* getEndpointHandlers(); + std::vector getPlaybackEndpoints(); + uint64_t getPlaybackEndpointsCount(); void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); - static Overseer* getOverseer(); + //static Overseer* getOverseer(); private: static Overseer os; - std::vector endpointWidgets; + std::vector endpointHandlers; //QSlider *slidy; //public slots: diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 70334c3..104f7da 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -63,7 +63,17 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( } -MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : QMainWindow(parent) { +void EndpointWidget::setIndex(uint64_t idx){ + this.idx = idx; +} + +uint64_t EndpointWidget::getIndex(){ + return idx; +} + + + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // setWindowState(Qt::WindowFullScreen); // setCentralWidget(centralWidget); widget = new QWidget(); @@ -81,11 +91,10 @@ MainWindow::MainWindow(std::vector *ephs, QWidget *parent) : Q unsigned int i = 0; for (; i < ephs->size(); i++) { log_debugcpp("EPWidget creation"); - EndpointWidget *epw = new EndpointWidget(ephs->at(i), widget); + EndpointWidget *epw = new EndpointWidget(osh->getEndpointHandlers().at(i), widget); ews.push_back(epw); layout->addWidget(epw, i, 0); } - osh->setEndpointWidgets(ews); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index e7e9d91..824fd89 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -21,7 +21,9 @@ public: EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr); //TODO: get(); EndpointHandler* eph; - + void setIndex(uint64_t idx); + void setVolume(int channel, float volume); + QPushButton *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; @@ -33,7 +35,7 @@ public: //void setEndpointHandlers(std::vector *ephs); private: - + uint64_t idx; //std::vector *ephs; //std::vector *sliders; diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index d619db1..5fa6360 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -22,17 +22,11 @@ QApplication* createApplication(int &argc, char *argv[]) int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); //INIT CONT - std::vector eps = OverseerHandler::getOverseer()->getPlaybackEndpoints(); - std::vector* ephs = new std::vector; - for(unsigned int i = 0; i < eps.size(); i++){ - EndpointCallback* epc = new EndpointCallback(); - EndpointHandler* eph = new EndpointHandler(eps.at(i), epc); - ephs->push_back(eph); - } + osh->reloadEndpointHandlers(); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); - MainWindow window = MainWindow(ephs); + MainWindow window = MainWindow(); //window.setEndpointHandlers(ephs); app->setStyle("windowsvista"); window.show(); From 4e10385a3b9255975b3b17a54f9e0bc3539db595 Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 11 Aug 2023 16:39:10 +0200 Subject: [PATCH 09/57] Refactored program structure --- qtest.pro | 1 + src/back/backlasses.cpp | 47 ++++++++++++++++--------- src/back/backlasses.h | 52 +++++++++++++++------------- src/cont/contclasses.cpp | 74 ++++++++++++++++++++++------------------ src/cont/contclasses.h | 33 +++++++++++------- src/debug.h | 1 + src/global.h | 6 ++++ src/qt/qtclasses.cpp | 6 ++-- src/qt/qtclasses.h | 9 +++-- src/qtestmain.cpp | 5 ++- 10 files changed, 139 insertions(+), 95 deletions(-) diff --git a/qtest.pro b/qtest.pro index 39fad14..73f3f80 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,6 +1,7 @@ QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 QMAKE_LINKER += clang++ QMAKE_LFLAGS += -v +DEFINES += DEBUG CONFIG += debug console QT += widgets INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 1a48dd7..1cf1006 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,7 +1,7 @@ #include EndpointCallback::EndpointCallback(Endpoint* ep){ - this.ep = ep; + this->ep = ep; } ULONG EndpointCallback::AddRef(){ @@ -36,13 +36,15 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { } HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { - if (pNotify == NULL) return E_INVALIDARG; - AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; - LPGUID guid = osh->getOverseer()->getGuid(); - - if (eventData.guidEventContext != *guid) { - osh->parseExternalEndpointCallback(this, eventData); - } + if (pNotify == NULL) return E_INVALIDARG; + /* + * AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; + * LPGUID guid = osh->getOverseer()->getGuid(); + * + * if (eventData.guidEventContext != *guid) { + * osh->parseExternalEndpointCallback(this, eventData); + * } + */ return S_OK; } @@ -58,11 +60,11 @@ Endpoint::Endpoint(IMMDevice* ep){ endpoint->OpenPropertyStore(STGM_READ, &properties); PROPVARIANT pv; properties->GetValue(PKEY_Device_FriendlyName , &pv); - friendlyName = pv.pwszVal; + friendlyName = std::wstring(pv.pwszVal); } void Endpoint::setIndex(uint64_t idx){ - this.idx = idx; + this->idx = idx; } uint64_t Endpoint::getIndex(){ @@ -70,7 +72,7 @@ uint64_t Endpoint::getIndex(){ } -LPWSTR Endpoint::getName(){ +std::wstring Endpoint::getName(){ return friendlyName; } @@ -118,10 +120,10 @@ void Endpoint::setVolume(int channel, float volume) { } void Endpoint::setMute() { - log_debugcpp("bool mute arrives as " << mut); BOOL mut; + //log_debugcpp("bool mute arrives as " << mut); if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - log_debugcpp("translate to BOOL as " << mute); + log_debugcpp("translate to BOOL as " << mut); if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), NULL))) { log_debugcpp("si"); }; } @@ -135,7 +137,6 @@ void Endpoint::removeCallback(EndpointCallback *epc){ Endpoint::~Endpoint(){ log_debugcpp("cum"); - free(friendlyName); properties->Release(); endpointVolume->Release(); endpoint->Release(); @@ -153,8 +154,18 @@ void Overseer::initCOMLibrary(){ (void**)&deviceEnumerator)) ) { log_debugcpp("si"); }; - if(FAILED(CoCreateGuid(guid))) { log_debugcpp("guyyyyyy"); }; - + /* + * LPGUID tempGuid = nullptr; + * if(FAILED(CoCreateGuid(tempGuid))) { log_debugcpp("Failed to obtain GUID"); }; + * + * this->guid.data1 = tempGuid->Data1; + * this->guid.data2 = tempGuid->Data2; + * this->guid.data3 = tempGuid->Data3; + * for (int i = 0; i < 8; i++){ + * this->guid.data4[i] = tempGuid->Data4[i]; + * } + */ + //TODO: Release lpguid? } void Overseer::reloadEndpoints() { @@ -184,6 +195,7 @@ void Overseer::reloadEndpoints() { Overseer::Overseer(){ //Initializing COM library + log_debugcpp("Initializing Overseer"); initCOMLibrary(); //Obtaining playback endpoint collection on this point in time @@ -198,7 +210,8 @@ Overseer::Overseer(){ //int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); -LPGUID Overseer::getGuid() { +//TODO guid +NGuid Overseer::getGuid() { return guid; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 2da5724..9f97dde 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,11 +1,13 @@ #pragma once #define WIN32_LEAN_AND_MEAN +//done by qt by def #define UNICODE -#include "debug.h" +//#include "debug.h" #include "global.h" -#include -#include -#include +#include "contclasses.h" +/* #include */ +/* #include */ +/* #include */ #include #include @@ -20,24 +22,9 @@ //#include #include -//class EndpointWidget; +#include "contclasses.h" -class EndpointCallback : public IAudioEndpointVolumeCallback { - - public: - EndpointCallback(Endpoint* ep); - - ULONG AddRef(); - ULONG Release(); - HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); - HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA update); - ~EndpointCallback(); - - private: - ULONG ref = 1; - Endpoint* ep; - PAUDIO_VOLUME_NOTIFICATION_DATA update; -}; +class EndpointCallback; class Endpoint { @@ -51,7 +38,7 @@ class Endpoint { float getVolume(int channel); void setMute(); bool getMute(); - LPWSTR getName(); + std::wstring getName(); void setCallback(EndpointCallback *epc); void removeCallback(EndpointCallback *epc); ~Endpoint(); @@ -65,13 +52,30 @@ class Endpoint { // LPWSTR endpointID = NULL; }; +class EndpointCallback : public IAudioEndpointVolumeCallback { + + public: + EndpointCallback(Endpoint* ep); + + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA update); + //~EndpointCallback(); + + private: + ULONG ref = 1; + Endpoint* ep; + //PAUDIO_VOLUME_NOTIFICATION_DATA update; +}; + class Overseer { //TODO singleton? public: Overseer(); std::vector getPlaybackEndpoints(); void reloadEndpoints(); - LPGUID getGuid(); + NGuid getGuid(); //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); @@ -80,7 +84,7 @@ class Overseer { ~Overseer(); private: - LPGUID guid; + NGuid guid; unsigned int numPlaybackEndpoints; IMMDeviceEnumerator *deviceEnumerator; std::vector playbackDevices; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index f5c2855..e26bf25 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,4 +1,4 @@ -#incluse "backlasses.h" +#include "backlasses.h" #include "contclasses.h" Overseer OverseerHandler::os; @@ -7,12 +7,12 @@ EndpointHandler::EndpointHandler(uint64_t idx) { std::vector endpoints = osh->getPlaybackEndpoints(); this->ep = endpoints.at(idx); epc = new EndpointCallback(ep); - epName = QString::fromStdWString(ept->getName()); - ep->setCallback(*epc); + //epName = ep->getName(); + //ep->setCallback(*epc); } void EndpointHandler::setIndex(uint64_t idx){ - this.idx = idx; + this->idx = idx; } uint64_t EndpointHandler::getIndex(){ @@ -25,36 +25,36 @@ uint64_t EndpointHandler::getIndex(){ */ void EndpointHandler::setValue(int channel, int value){ if (channel == ENDPOINT_MASTER_VOLUME) - ept->setVolume(channel, (float)value / 100); - else ept->setVolume(channel, (float)value / 100); + ep->setVolume(channel, (float)value / 100); + else ep->setVolume(channel, (float)value / 100); } void EndpointHandler::setMute(){ //Qt momento, de ahi el param? log_debugcpp("kinda handling the muting tbh"); - ept->setMute(); + ep->setMute(); } -QString EndpointHandler::getName(){ - return eptName; +std::wstring EndpointHandler::getName(){ + return ep->getName(); } float EndpointHandler::getVolume(int channel){ - return ept->getVolume(channel); + return ep->getVolume(channel); } bool EndpointHandler::getMute(){ - return ept->getMute(); + return ep->getMute(); } EndpointHandler::~EndpointHandler() { - ep->removeCallback(*epc); + ep->removeCallback(epc); delete epc; delete ep; } std::vector OverseerHandler::getPlaybackEndpoints() { - return os->getPlaybackEndpoints(); + return os.getPlaybackEndpoints(); } /* @@ -64,38 +64,46 @@ std::vector OverseerHandler::getPlaybackEndpoints() { */ -std::vector* OverseerHandler::getEndpointHandlers(){ - return &endpointHandlers; +std::vector OverseerHandler::getEndpointHandlers(){ + return endpointHandlers; } uint64_t OverseerHandler::getPlaybackEndpointsCount(){ - return os->getplaybackEndpoints().size(); + return os.getPlaybackEndpoints().size(); } void OverseerHandler::reloadEndpointHandlers(){ //std::vector* ephs = new std::vector; + log_debugcpp(" VSize: " << this->getPlaybackEndpointsCount()); + + for(uint64_t i = 0; i < this->getPlaybackEndpointsCount(); i++){ + log_debugcpp("Creating handler " << i); + + if(i < (this->endpointHandlers.size()) && + this->endpointHandlers.at(i) != nullptr) + delete endpointHandlers.at(i); + - for(uint64_t i = 0; i < osh->getPlaybackEndpointsCount(); i++){ - if(i < osh->getPlaybackEndpointsCount().size() && - osh->getPlaybackEndpointsCount().at(i) != nullptr) - delete ephs.at(i); EndpointHandler* eph = new EndpointHandler(i); - - if (i >= osh->getPlaybackEndpointsCount().size()) - ephs.push_back(eph); - else epsh.at(i) = eph; + log_debugcpp("Created handler " << i << ", adding to vector. " << " VSize: " << this->getPlaybackEndpointsCount()); + + if (i >= this->endpointHandlers.size()) + endpointHandlers.push_back(eph); + else endpointHandlers.at(i) = eph; } //setEndpointHandlers(ephs); } -void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify){ - log_debugcpp("parsing in da ovasiar"); - for (uint64_t i = 0; i < endpointHandlers.size(); i++){ - if(endpointHandlers.at(i)->eph->epc == fEpc) { - endpointHandlers.at(i)->muteButton->setText(endpointHandlers.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - break; - } - } +/* + * void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify){ + * log_debugcpp("parsing in da ovasiar"); + * for (uint64_t i = 0; i < endpointHandlers.size(); i++){ + * if(endpointHandlers.at(i)->eph->epc == fEpc) { + * endpointHandlers.at(i)->muteButton->setText(endpointHandlers.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + * break; + * } + * } + */ /* * connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); @@ -104,7 +112,7 @@ void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUD * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); * log_debugcpp("ENDPOINT_WIDGETED"); */ -} + void OverseerHandler::setEndpointHandlers(std::vector ews){ this->endpointHandlers = ews; diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index ceeb17c..db013b0 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,27 +1,36 @@ #pragma once -#include "backlasses.h" +class Endpoint; +class EndpointCallback; +class Overseer; +struct NGuid { + uint32_t data1; + uint16_t data2; + uint16_t data3; + unsigned char data4[8]; +}; class EndpointHandler { public: - EndpointHandler(Endpoint *ept); + EndpointHandler(uint64_t idx); //TODO: get(); Endpoint *ep; EndpointCallback *epc; - QString epName; + //std::wstring epName; void setIndex(uint64_t idx); + uint64_t getIndex(); void setVolume(int channel, float volume); - //todo qstrin????? idiota - QString getName(); + std::wstring getName(); float getVolume(int channel); bool getMute(); void setValue(int channel, int value); - void setMute(); + void setMute(); + ~EndpointHandler(); private: uint64_t idx; //QSlider *slidy; @@ -37,18 +46,16 @@ class OverseerHandler { public: //OverseerHandler(); void setEndpointHandlers(std::vector ews); - std::vector* getEndpointHandlers(); + std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); - void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); + void reloadEndpointHandlers(); + + //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); private: static Overseer os; std::vector endpointHandlers; - //QSlider *slidy; - - //public slots: - //void setValue(int value); - + }; diff --git a/src/debug.h b/src/debug.h index f91232b..6e4baa8 100644 --- a/src/debug.h +++ b/src/debug.h @@ -8,3 +8,4 @@ #else #define log_debugcpp(str) #endif + diff --git a/src/global.h b/src/global.h index 977e0b8..6b949b6 100644 --- a/src/global.h +++ b/src/global.h @@ -1,5 +1,11 @@ #pragma once +#include +#include +#include + +#include "debug.h" + //TODO enum capullo #define ENDPOINT_MASTER_VOLUME -1 #define ENDPOINT_LEFT_CHANNEL_VOLUME 0 diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 104f7da..79c1909 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -9,7 +9,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( if (parent == nullptr) { log_debugcpp("owo?"); } muteButton = new QPushButton(); - mainLabel = new QLabel(eph->getName()); + mainLabel = new QLabel(QString::fromStdWString(eph->getName())); leftChannelLabel = new QLabel("88"); rightChannelLabel = new QLabel("77"); mainSlider = new QSlider(Qt::Horizontal); @@ -64,7 +64,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( void EndpointWidget::setIndex(uint64_t idx){ - this.idx = idx; + this->idx = idx; } uint64_t EndpointWidget::getIndex(){ @@ -89,7 +89,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { * setEndpointHandlers(ephs); */ unsigned int i = 0; - for (; i < ephs->size(); i++) { + for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); EndpointWidget *epw = new EndpointWidget(osh->getEndpointHandlers().at(i), widget); ews.push_back(epw); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 824fd89..a543c25 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -2,13 +2,16 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include +//#include #include #include #include #include #include #include + +//#include "debug.h" +#include "global.h" #include "contclasses.h" //#include //#include @@ -22,6 +25,8 @@ public: //TODO: get(); EndpointHandler* eph; void setIndex(uint64_t idx); + uint64_t getIndex(); + void setVolume(int channel, float volume); QPushButton *muteButton = nullptr; @@ -53,7 +58,7 @@ class MainWindow : public QMainWindow { //QWidget *centralWidget; public: - MainWindow(std::vector *ephs, QWidget *parent = nullptr); + MainWindow(QWidget *parent = nullptr); //void setEndpointHandlers(std::vector *ephs); private: diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 5fa6360..453cce6 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -1,6 +1,3 @@ -#include -#include - //#include //#include @@ -22,7 +19,9 @@ QApplication* createApplication(int &argc, char *argv[]) int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); //INIT CONT + log_debugcpp("main init"); osh->reloadEndpointHandlers(); + log_debugcpp("Reloaded endpoint handlers"); //INIT FRONT QScopedPointer app(createApplication(argc, argv)); From 44461afdc4e36880cdc3d5bbb2d23231a42b0260 Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 11 Aug 2023 21:35:48 +0200 Subject: [PATCH 10/57] template and fixed guid obtaining --- src/back/backlasses.cpp | 37 +++++++++++++++++++++++++------------ src/debug.h | 14 +++++++++++++- src/global.h | 1 + 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 1cf1006..1a6f463 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -144,7 +144,8 @@ Endpoint::~Endpoint(){ void Overseer::initCOMLibrary(){ - if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { log_debugcpp("si"); }; + if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { + log_debugcpp("si"); }; //Retrieving endpoint enumerator @@ -154,17 +155,29 @@ void Overseer::initCOMLibrary(){ (void**)&deviceEnumerator)) ) { log_debugcpp("si"); }; - /* - * LPGUID tempGuid = nullptr; - * if(FAILED(CoCreateGuid(tempGuid))) { log_debugcpp("Failed to obtain GUID"); }; - * - * this->guid.data1 = tempGuid->Data1; - * this->guid.data2 = tempGuid->Data2; - * this->guid.data3 = tempGuid->Data3; - * for (int i = 0; i < 8; i++){ - * this->guid.data4[i] = tempGuid->Data4[i]; - * } - */ + + GUID tempGuid; + /* + * HRESULT her = CoCreateGuid(&tempGuid); + * std::bitset<32> bon(her); + * //if (hr == EPT_S_CANT_CREATE) + * log_debugcpp("Failed to obtain GUID: " /\*<< std::hex *\/<< her << std::dec << " " << bon); + */ + if(FAILED(CoCreateGuid(&tempGuid))) { log_debugcpp("Failed to obtain GUID: " ); }; + + + this->guid.data1 = tempGuid.Data1; + this->guid.data2 = tempGuid.Data2; + this->guid.data3 = tempGuid.Data3; + for (int i = 0; i < 8; i++){ + this->guid.data4[i] = tempGuid.Data4[i]; + log_debugcpp("GUID DATA4 BYTE " << i << ": "); + log_debugcpp(print_as_binary(8, uint32_t, this->guid.data4[i])); + } + log_debugcpp("GUID DATA1: " << this->guid.data1); + log_debugcpp("GUID DATA2: " << this->guid.data2); + log_debugcpp("GUID DATA3: " << this->guid.data3); + //TODO: Release lpguid? } diff --git a/src/debug.h b/src/debug.h index 6e4baa8..78cb19c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -2,10 +2,22 @@ #if defined (QT_DEBUG) || defined (DEBUG) || defined (_DEBUG) +template +std::bitset varToBitset(T info) { + std::bitset content(info); + return content; +} + #define log_debugcpp(str) do { \ std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ } while (0) + +#define print_as_binary(len, type, info) varToBitset(info) + #else -#define log_debugcpp(str) +#define log_debugcpp(str) +#define print_as_binary(len, info) #endif + + diff --git a/src/global.h b/src/global.h index 6b949b6..eaf2968 100644 --- a/src/global.h +++ b/src/global.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "debug.h" From c8d64481e896486eb1a0837a3e371434ef12bf44 Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 12 Aug 2023 12:29:01 +0200 Subject: [PATCH 11/57] guid sent and validated for mute --- qtest.pro | 2 +- src/back/backfuncs.h | 33 +++++++++++++++++++++++ src/back/backlasses.cpp | 56 ++++++++++++++++------------------------ src/back/backlasses.h | 7 +++-- src/cont/contclasses.cpp | 28 +++++--------------- src/cont/contclasses.h | 7 ++--- src/qt/qtclasses.cpp | 2 +- 7 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 src/back/backfuncs.h diff --git a/qtest.pro b/qtest.pro index 73f3f80..d2ac80f 100644 --- a/qtest.pro +++ b/qtest.pro @@ -8,5 +8,5 @@ INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp -HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h +HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h #DESTDIR += "build" diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h new file mode 100644 index 0000000..13f36bc --- /dev/null +++ b/src/back/backfuncs.h @@ -0,0 +1,33 @@ +GUID NGuidToGUID(NGuid* guid){ + GUID msGuid = GUID(); + msGuid.Data1 = guid->data1; + msGuid.Data2 = guid->data2; + msGuid.Data3 = guid->data3; + msGuid.Data1 = guid->data1; + for (int i = 0; i < 8; i++){ + msGuid.Data4[i] = guid->data4[i]; + log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); + log_debugcpp(print_as_binary(8, uint32_t, msGuid.Data4[i])); + } + log_debugcpp("MSGUID DATA1: " << msGuid.Data1); + log_debugcpp("MSGUID DATA2: " << msGuid.Data2); + log_debugcpp("MSGUID DATA3: " << msGuid.Data3); + + return msGuid; +} + +NGuid GUIDToNGuid(LPGUID msGuid){ + NGuid guid = NGuid(); + guid.data1 = msGuid->Data1; + guid.data2 = msGuid->Data2; + guid.data3 = msGuid->Data3; + for (int i = 0; i < 8; i++){ + guid.data4[i] = msGuid->Data4[i]; + log_debugcpp("GUID DATA4 BYTE " << i << ": "); + log_debugcpp(print_as_binary(8, uint32_t, guid.data4[i])); + } + log_debugcpp("GUID DATA1: " << guid.data1); + log_debugcpp("GUID DATA2: " << guid.data2); + log_debugcpp("GUID DATA3: " << guid.data3); + return guid; +} diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 1a6f463..e56da14 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,4 +1,5 @@ #include +#include EndpointCallback::EndpointCallback(Endpoint* ep){ this->ep = ep; @@ -37,14 +38,17 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - /* - * AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; - * LPGUID guid = osh->getOverseer()->getGuid(); - * - * if (eventData.guidEventContext != *guid) { - * osh->parseExternalEndpointCallback(this, eventData); - * } - */ + + AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; + NGuid* guid = osh->getGuid(); + + if (memcmp(guid, &eventData, sizeof(guid)) == 0) { + log_debugcpp("perlitas"); + } else { + log_debugcpp("Onnanokotify says Stored: " << guid->data1); + log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); + } + return S_OK; } @@ -119,12 +123,14 @@ void Endpoint::setVolume(int channel, float volume) { } } -void Endpoint::setMute() { +void Endpoint::setMute(NGuid* guid) { BOOL mut; //log_debugcpp("bool mute arrives as " << mut); if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - log_debugcpp("translate to BOOL as " << mut); - if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), NULL))) { log_debugcpp("si"); }; + log_debugcpp("translate to BOOL as " << mut); + //TODO: use new funcs + GUID tempMsGuid = NGuidToGUID(guid); + if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), &tempMsGuid))) { log_debugcpp("si"); }; } void Endpoint::setCallback(EndpointCallback *epc){ @@ -142,8 +148,7 @@ Endpoint::~Endpoint(){ endpoint->Release(); } - -void Overseer::initCOMLibrary(){ +void Overseer::initCOMLibrary() { if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { log_debugcpp("si"); }; @@ -155,30 +160,13 @@ void Overseer::initCOMLibrary(){ (void**)&deviceEnumerator)) ) { log_debugcpp("si"); }; - GUID tempGuid; - /* - * HRESULT her = CoCreateGuid(&tempGuid); - * std::bitset<32> bon(her); - * //if (hr == EPT_S_CANT_CREATE) - * log_debugcpp("Failed to obtain GUID: " /\*<< std::hex *\/<< her << std::dec << " " << bon); - */ if(FAILED(CoCreateGuid(&tempGuid))) { log_debugcpp("Failed to obtain GUID: " ); }; - - this->guid.data1 = tempGuid.Data1; - this->guid.data2 = tempGuid.Data2; - this->guid.data3 = tempGuid.Data3; - for (int i = 0; i < 8; i++){ - this->guid.data4[i] = tempGuid.Data4[i]; - log_debugcpp("GUID DATA4 BYTE " << i << ": "); - log_debugcpp(print_as_binary(8, uint32_t, this->guid.data4[i])); - } - log_debugcpp("GUID DATA1: " << this->guid.data1); - log_debugcpp("GUID DATA2: " << this->guid.data2); - log_debugcpp("GUID DATA3: " << this->guid.data3); + this->guid = GUIDToNGuid(&tempGuid); //TODO: Release lpguid? + //TODO: Uninitialize COM } void Overseer::reloadEndpoints() { @@ -224,8 +212,8 @@ Overseer::Overseer(){ //int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); //TODO guid -NGuid Overseer::getGuid() { - return guid; +NGuid* Overseer::getGuid() { + return &guid; } std::vector Overseer::getPlaybackEndpoints() { diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 9f97dde..3be9f2d 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -3,8 +3,6 @@ //done by qt by def #define UNICODE //#include "debug.h" -#include "global.h" -#include "contclasses.h" /* #include */ /* #include */ /* #include */ @@ -22,6 +20,7 @@ //#include #include +#include "global.h" #include "contclasses.h" class EndpointCallback; @@ -36,7 +35,7 @@ class Endpoint { /* float getLeftChannelVolume(); */ /* float getRightChannelVolume(); */ float getVolume(int channel); - void setMute(); + void setMute(NGuid* guid); bool getMute(); std::wstring getName(); void setCallback(EndpointCallback *epc); @@ -75,7 +74,7 @@ class Overseer { Overseer(); std::vector getPlaybackEndpoints(); void reloadEndpoints(); - NGuid getGuid(); + NGuid* getGuid(); //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index e26bf25..85bb2fb 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -8,7 +8,7 @@ EndpointHandler::EndpointHandler(uint64_t idx) { this->ep = endpoints.at(idx); epc = new EndpointCallback(ep); //epName = ep->getName(); - //ep->setCallback(*epc); + ep->setCallback(epc); } void EndpointHandler::setIndex(uint64_t idx){ @@ -29,10 +29,10 @@ void EndpointHandler::setValue(int channel, int value){ else ep->setVolume(channel, (float)value / 100); } -void EndpointHandler::setMute(){ +void EndpointHandler::setMute(NGuid* guid){ //Qt momento, de ahi el param? log_debugcpp("kinda handling the muting tbh"); - ep->setMute(); + ep->setMute(guid); } std::wstring EndpointHandler::getName(){ @@ -94,24 +94,10 @@ void OverseerHandler::reloadEndpointHandlers(){ //setEndpointHandlers(ephs); } -/* - * void OverseerHandler::parseExternalEndpointCallback(EndpointCallback *fEpc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify){ - * log_debugcpp("parsing in da ovasiar"); - * for (uint64_t i = 0; i < endpointHandlers.size(); i++){ - * if(endpointHandlers.at(i)->eph->epc == fEpc) { - * endpointHandlers.at(i)->muteButton->setText(endpointHandlers.at(i)->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - * break; - * } - * } - */ - - /* - * connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); - * connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); - * connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); - * log_debugcpp("ENDPOINT_WIDGETED"); - */ + +NGuid* OverseerHandler::getGuid() { + return os.getGuid(); +} void OverseerHandler::setEndpointHandlers(std::vector ews){ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index db013b0..c7e7ecf 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -16,8 +16,8 @@ class EndpointHandler { public: EndpointHandler(uint64_t idx); //TODO: get(); - Endpoint *ep; - EndpointCallback *epc; + Endpoint *ep = nullptr; + EndpointCallback *epc = nullptr; //std::wstring epName; void setIndex(uint64_t idx); @@ -29,7 +29,7 @@ public: bool getMute(); void setValue(int channel, int value); - void setMute(); + void setMute(NGuid* guid); ~EndpointHandler(); private: uint64_t idx; @@ -50,6 +50,7 @@ public: std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); + NGuid* getGuid(); //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 79c1909..d675be1 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -58,7 +58,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); + connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); log_debugcpp("ENDPOINT_WIDGETED"); } From a565190e6f4acd81aec2a8638fef1c3433b86afa Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 12 Aug 2023 17:29:01 +0200 Subject: [PATCH 12/57] reverted failed attempt, fixed callback release --- src/back/backlasses.cpp | 2 +- src/back/backlasses.h | 1 - src/cont/contclasses.cpp | 23 +++++++++++++++-------- src/cont/contclasses.h | 13 +++++++++++-- src/qt/qtclasses.cpp | 18 ++++++------------ src/qt/qtclasses.h | 21 +++++++++++++++------ src/qtestmain.cpp | 3 +++ 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index e56da14..36dc9bf 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -42,7 +42,7 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; NGuid* guid = osh->getGuid(); - if (memcmp(guid, &eventData, sizeof(guid)) == 0) { + if (memcmp(guid, &eventData, sizeof(*guid)) == 0) { log_debugcpp("perlitas"); } else { log_debugcpp("Onnanokotify says Stored: " << guid->data1); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 3be9f2d..0176ebf 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -65,7 +65,6 @@ class EndpointCallback : public IAudioEndpointVolumeCallback { private: ULONG ref = 1; Endpoint* ep; - //PAUDIO_VOLUME_NOTIFICATION_DATA update; }; class Overseer { diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 85bb2fb..1b58317 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,6 +1,9 @@ #include "backlasses.h" +#include "qtclasses.h" #include "contclasses.h" +//TODO: pragma once +//TODO: ????? Overseer OverseerHandler::os; EndpointHandler::EndpointHandler(uint64_t idx) { @@ -49,12 +52,12 @@ bool EndpointHandler::getMute(){ EndpointHandler::~EndpointHandler() { ep->removeCallback(epc); - delete epc; + epc->Release(); delete ep; } std::vector OverseerHandler::getPlaybackEndpoints() { - return os.getPlaybackEndpoints(); + return this->os.getPlaybackEndpoints(); } /* @@ -69,7 +72,7 @@ std::vector OverseerHandler::getEndpointHandlers(){ } uint64_t OverseerHandler::getPlaybackEndpointsCount(){ - return os.getPlaybackEndpoints().size(); + return this->os.getPlaybackEndpoints().size(); } void OverseerHandler::reloadEndpointHandlers(){ @@ -83,7 +86,6 @@ void OverseerHandler::reloadEndpointHandlers(){ this->endpointHandlers.at(i) != nullptr) delete endpointHandlers.at(i); - EndpointHandler* eph = new EndpointHandler(i); log_debugcpp("Created handler " << i << ", adding to vector. " << " VSize: " << this->getPlaybackEndpointsCount()); @@ -96,11 +98,16 @@ void OverseerHandler::reloadEndpointHandlers(){ NGuid* OverseerHandler::getGuid() { - return os.getGuid(); + return this->os.getGuid(); } - -void OverseerHandler::setEndpointHandlers(std::vector ews){ - this->endpointHandlers = ews; +void OverseerHandler::setEndpointHandlers(std::vector ephs){ + this->endpointHandlers = ephs; } +/* + * void OverseerHandler::setEndpointWidgets(std::vector ews){ + * this->endpointWidgets = ews; + * } + */ + diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index c7e7ecf..2a27a2e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,5 +1,13 @@ #pragma once +/* #ifndef QTBLESSED */ +/* //#define Q_OBJECT */ +/* class QWidget{}; */ +/* class QMainWindow{}; */ +/* #endif */ + +/* class EndpointWidget; */ + class Endpoint; class EndpointCallback; class Overseer; @@ -45,7 +53,8 @@ class OverseerHandler { public: //OverseerHandler(); - void setEndpointHandlers(std::vector ews); + void setEndpointHandlers(std::vector ephs); + /* void setEndpointWidgets(std::vector ews); */ std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); @@ -58,5 +67,5 @@ public: private: static Overseer os; std::vector endpointHandlers; - + /* std::vector endpointWidgets; */ }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index d675be1..9f10764 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,6 +1,5 @@ #include "qtclasses.h" - EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget(parent){ this->eph = eph; layout = new QGridLayout(); @@ -71,8 +70,6 @@ uint64_t EndpointWidget::getIndex(){ return idx; } - - MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // setWindowState(Qt::WindowFullScreen); // setCentralWidget(centralWidget); @@ -84,10 +81,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { //layout->addWidget(pintas, 0, 0); setWindowTitle("slidea resbala nu c"); + + reloadEndpointWidgets(); +} - /*s - * setEndpointHandlers(ephs); - */ +void MainWindow::reloadEndpointWidgets() { unsigned int i = 0; for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); @@ -95,15 +93,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { ews.push_back(epw); layout->addWidget(epw, i, 0); } + //osh->setEndpointWidgets(ews); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } -/* - * void MainWindow::setEndpointHandlers(std::vector *ephs){ - * this->ephs = ephs; - */ - - +//#include "qtclosemwh.h" /* * void MainWindow::setPlotButton() { diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index a543c25..0a18ac9 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -1,8 +1,8 @@ #pragma once + #ifndef MAINWINDOW_H #define MAINWINDOW_H -//#include #include #include #include @@ -10,12 +10,20 @@ #include #include -//#include "debug.h" +/* + * #else + * class QSlider; + * class QLabel; + * class QGridLayout; + * class QPushButton; + * class QWidget; + * class QMainWindow; + * #endif + */ + #include "global.h" #include "contclasses.h" -//#include -//#include - +//class EndpointHandler; class EndpointWidget : public QWidget { Q_OBJECT @@ -59,6 +67,8 @@ class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr); + void reloadEndpointWidgets(); + //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); private: @@ -77,4 +87,3 @@ private: }; #endif - diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 453cce6..15d18fc 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -2,9 +2,12 @@ //#include //#include + +//#define QTBLESSED #include #include +//#include "contclasses.h" #include "qtclasses.h" #include "global.h" From 167331944cb967a94a518331ec1050e727daf5a7 Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 12 Aug 2023 19:26:50 +0200 Subject: [PATCH 13/57] first callback implemented; overload not working --- src/back/backlasses.cpp | 13 ++++++------ src/back/backlasses.h | 2 +- src/cont/contclasses.cpp | 17 +++++++++------ src/cont/contclasses.h | 9 ++++---- src/qt/qtclasses.cpp | 46 ++++++++++++++++++++++++++++++++++------ src/qt/qtclasses.h | 34 +++++++++++++++++++++++++---- 6 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 36dc9bf..b9c54c8 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -43,10 +43,11 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { NGuid* guid = osh->getGuid(); if (memcmp(guid, &eventData, sizeof(*guid)) == 0) { - log_debugcpp("perlitas"); + log_debugcpp("Onnanokotify says You Shall Not Update Thy Interface."); } else { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); + osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); } return S_OK; @@ -123,14 +124,14 @@ void Endpoint::setVolume(int channel, float volume) { } } -void Endpoint::setMute(NGuid* guid) { - BOOL mut; +void Endpoint::setMute(NGuid* guid, bool muted) { + //BOOL mut; //log_debugcpp("bool mute arrives as " << mut); - if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - log_debugcpp("translate to BOOL as " << mut); + //if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } + //log_debugcpp("translate to BOOL as " << mut); //TODO: use new funcs GUID tempMsGuid = NGuidToGUID(guid); - if(FAILED(endpointVolume->SetMute((mut == false ? 1 : 0), &tempMsGuid))) { log_debugcpp("si"); }; + if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_debugcpp("si"); }; } void Endpoint::setCallback(EndpointCallback *epc){ diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 0176ebf..386344f 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -35,7 +35,7 @@ class Endpoint { /* float getLeftChannelVolume(); */ /* float getRightChannelVolume(); */ float getVolume(int channel); - void setMute(NGuid* guid); + void setMute(NGuid* guid, bool muted); bool getMute(); std::wstring getName(); void setCallback(EndpointCallback *epc); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 1b58317..81e71cb 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -32,10 +32,10 @@ void EndpointHandler::setValue(int channel, int value){ else ep->setVolume(channel, (float)value / 100); } -void EndpointHandler::setMute(NGuid* guid){ +void EndpointHandler::setMute(NGuid* guid, bool muted){ //Qt momento, de ahi el param? log_debugcpp("kinda handling the muting tbh"); - ep->setMute(guid); + ep->setMute(guid, muted); } std::wstring EndpointHandler::getName(){ @@ -105,9 +105,12 @@ void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; } -/* - * void OverseerHandler::setEndpointWidgets(std::vector ews){ - * this->endpointWidgets = ews; - * } - */ +void OverseerHandler::setEndpointWidgets(std::vector ews){ + this->endpointWidgets = ews; +} + +void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ + epwMuteFunc f = &EndpointWidget::updateMuteC; + std::invoke(f, endpointWidgets.at(idx), muted); +} diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 2a27a2e..16cab7c 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -6,7 +6,7 @@ /* class QMainWindow{}; */ /* #endif */ -/* class EndpointWidget; */ +class EndpointWidget; class Endpoint; class EndpointCallback; @@ -37,7 +37,7 @@ public: bool getMute(); void setValue(int channel, int value); - void setMute(NGuid* guid); + void setMute(NGuid* guid, bool muted); ~EndpointHandler(); private: uint64_t idx; @@ -54,12 +54,13 @@ class OverseerHandler { public: //OverseerHandler(); void setEndpointHandlers(std::vector ephs); - /* void setEndpointWidgets(std::vector ews); */ + void setEndpointWidgets(std::vector ews); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); NGuid* getGuid(); + void updateMuteCallback(uint64_t idx, bool muted); //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); @@ -67,5 +68,5 @@ public: private: static Overseer os; std::vector endpointHandlers; - /* std::vector endpointWidgets; */ + std::vector endpointWidgets; }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 9f10764..1a48c91 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,5 +1,21 @@ #include "qtclasses.h" +/* + * ToggleButton::ToggleButton(QWidget *parent) : QAbstractButton(parent) { + * this->setCheckable(true); + * } + * + * ToggleButton::~ToggleButton(){ + * + * } + * + * void ToggleButton::checkStateSet(){ } + * + * bool hitButton(const QPoint &pos) { + * + * } + */ + EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget(parent){ this->eph = eph; layout = new QGridLayout(); @@ -7,7 +23,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( log_debugcpp("olaW"); if (parent == nullptr) { log_debugcpp("owo?"); } - muteButton = new QPushButton(); + muteButton = new QCheckBox(); mainLabel = new QLabel(QString::fromStdWString(eph->getName())); leftChannelLabel = new QLabel("88"); rightChannelLabel = new QLabel("77"); @@ -15,7 +31,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( leftChannelSlider = new QSlider(Qt::Horizontal); rightChannelSlider = new QSlider(Qt::Horizontal); - muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); + //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); mainSlider->setFocusPolicy(Qt::StrongFocus); mainSlider->setTickPosition(QSlider::TicksBothSides); mainSlider->setTickInterval(5); @@ -28,6 +44,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( rightChannelSlider->setSingleStep(1); rightChannelSlider->setRange(0,100); + muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; mainSlider->setValue((int)volume); @@ -51,16 +68,31 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); - /* - * connect(mainSlider, &QSlider::valueChanged, eph, &EndpointHandler::setValue); - */ connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); + connect(muteButton, &QCheckBox::stateChanged, this, &EndpointWidget::updateMute); + /* + * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); + */ log_debugcpp("ENDPOINT_WIDGETED"); } +void EndpointWidget::updateMuteC(bool muted){ + log_debugcpp("cliqui callboqui cloqui"); + this->eph->setMute(osh->getGuid(), muted); + this->muteButton->setChecked(eph->getMute() ? true : false); + this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); +} + +void EndpointWidget::updateMute(int checked){ + log_debugcpp("cliqui slOtty cloqui"); + bool muted = (checked == 2 ? true : false); + log_debugcpp("int: " << checked << " bool: " << muted); + this->eph->setMute(osh->getGuid(), muted); + //this->muteButton->setCheckState(); + this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); +} void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; @@ -93,7 +125,7 @@ void MainWindow::reloadEndpointWidgets() { ews.push_back(epw); layout->addWidget(epw, i, 0); } - //osh->setEndpointWidgets(ews); + osh->setEndpointWidgets(ews); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 0a18ac9..d4d1d31 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -9,7 +9,7 @@ #include #include #include - +#include /* * #else * class QSlider; @@ -25,6 +25,30 @@ #include "contclasses.h" //class EndpointHandler; +/* + * class ToggleButton : public QAbstractButton { + * Q_OBJECT + * + * public: + * ToggleButton(QWidget *parent = nullptr); + * void checkStateSet(); + * bool hitButton(const QPoint &pos) const; + * void nextCheckState(); + * void changeEvent(QEvent *e) override; + * bool event(QEvent *e) override; + * void focusInEvent(QFocusEvent *e) override; + * void focusOutEvent(QFocusEvent *e) override; + * void keyPressEvent(QKeyEvent *e) override; + * void keyReleaseEvent(QKeyEvent *e) override; + * void mouseMoveEvent(QMouseEvent *e) override; + * void mousePressEvent(QMouseEvent *e) override; + * void mouseReleaseEvent(QMouseEvent *e) override; + * void paintEvent(QPaintEvent *e) override = 0; + * void timerEvent(QTimerEvent *e) override; + * ToggleButton(QWidget *parent = nullptr); + * }; + */ + class EndpointWidget : public QWidget { Q_OBJECT @@ -37,29 +61,31 @@ public: void setVolume(int channel, float volume); - QPushButton *muteButton = nullptr; + QCheckBox *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; QSlider *leftChannelSlider = nullptr; QSlider *rightChannelSlider = nullptr; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; + void updateMuteC(bool muted); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); +public slots: + void updateMute(int checked); private: uint64_t idx; //std::vector *ephs; //std::vector *sliders; - //public slots: - // void setEndpointHandlers(std::vector *ephs); //signals: //void valueChanged(int value); }; +typedef void (EndpointWidget::*epwMuteFunc)(bool muted); class MainWindow : public QMainWindow { Q_OBJECT From a251b4cb6be569109dd02777192ced8c5985a3ba Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 12 Aug 2023 19:48:22 +0200 Subject: [PATCH 14/57] fixed overload funcs and connect() --- src/cont/contclasses.cpp | 2 +- src/qt/qtclasses.cpp | 4 ++-- src/qt/qtclasses.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 81e71cb..9df5822 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -111,6 +111,6 @@ void OverseerHandler::setEndpointWidgets(std::vector ews){ } void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ - epwMuteFunc f = &EndpointWidget::updateMuteC; + epwMuteFunc f = &EndpointWidget::updateMute; std::invoke(f, endpointWidgets.at(idx), muted); } diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 1a48c91..082766e 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -71,14 +71,14 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - connect(muteButton, &QCheckBox::stateChanged, this, &EndpointWidget::updateMute); + connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); /* * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); */ log_debugcpp("ENDPOINT_WIDGETED"); } -void EndpointWidget::updateMuteC(bool muted){ +void EndpointWidget::updateMute(bool muted){ log_debugcpp("cliqui callboqui cloqui"); this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(eph->getMute() ? true : false); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index d4d1d31..3f2afb9 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -68,7 +68,7 @@ public: QSlider *rightChannelSlider = nullptr; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; - void updateMuteC(bool muted); + void updateMute(bool muted); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); public slots: From 679ad34f844da5cc51b0c128bec9cc3bd61454a6 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Aug 2023 15:25:34 +0200 Subject: [PATCH 15/57] main slider cbk, detected minor l_dcpp bug --- src/back/backfuncs.h | 22 +++++++++++----------- src/back/backlasses.cpp | 8 +++++--- src/back/backlasses.h | 2 +- src/cont/contclasses.cpp | 13 ++++++++++--- src/cont/contclasses.h | 3 ++- src/qt/qtclasses.cpp | 20 +++++++++++++++++--- src/qt/qtclasses.h | 3 +++ 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h index 13f36bc..06adc7e 100644 --- a/src/back/backfuncs.h +++ b/src/back/backfuncs.h @@ -1,4 +1,4 @@ -GUID NGuidToGUID(NGuid* guid){ +GUID NGuidToGUID(NGuid* guid) { GUID msGuid = GUID(); msGuid.Data1 = guid->data1; msGuid.Data2 = guid->data2; @@ -6,12 +6,12 @@ GUID NGuidToGUID(NGuid* guid){ msGuid.Data1 = guid->data1; for (int i = 0; i < 8; i++){ msGuid.Data4[i] = guid->data4[i]; - log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); - log_debugcpp(print_as_binary(8, uint32_t, msGuid.Data4[i])); + //log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); + //log_debugcpp(print_as_binary(8, uint32_t, msGuid.Data4[i])); } - log_debugcpp("MSGUID DATA1: " << msGuid.Data1); - log_debugcpp("MSGUID DATA2: " << msGuid.Data2); - log_debugcpp("MSGUID DATA3: " << msGuid.Data3); + //log_debugcpp("MSGUID DATA1: " << msGuid.Data1); + //log_debugcpp("MSGUID DATA2: " << msGuid.Data2); + //log_debugcpp("MSGUID DATA3: " << msGuid.Data3); return msGuid; } @@ -23,11 +23,11 @@ NGuid GUIDToNGuid(LPGUID msGuid){ guid.data3 = msGuid->Data3; for (int i = 0; i < 8; i++){ guid.data4[i] = msGuid->Data4[i]; - log_debugcpp("GUID DATA4 BYTE " << i << ": "); - log_debugcpp(print_as_binary(8, uint32_t, guid.data4[i])); + //log_debugcpp("GUID DATA4 BYTE " << i << ": "); + //log_debugcpp(print_as_binary(8, uint32_t, guid.data4[i])); } - log_debugcpp("GUID DATA1: " << guid.data1); - log_debugcpp("GUID DATA2: " << guid.data2); - log_debugcpp("GUID DATA3: " << guid.data3); + //log_debugcpp("GUID DATA1: " << guid.data1); + //log_debugcpp("GUID DATA2: " << guid.data2); + //log_debugcpp("GUID DATA3: " << guid.data3); return guid; } diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index b9c54c8..6500e1c 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -48,6 +48,7 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); + osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); } return S_OK; @@ -116,11 +117,12 @@ bool Endpoint::getMute(){ */ -void Endpoint::setVolume(int channel, float volume) { +void Endpoint::setVolume(NGuid* guid, int channel, float volume) { + GUID tempMsGuid = NGuidToGUID(guid); if (channel == ENDPOINT_MASTER_VOLUME) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, NULL))) { log_debugcpp("si"); }; + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("si"); }; } else { - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, NULL))) { log_debugcpp("si"); }; + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("si"); }; } } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 386344f..8e0b943 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -31,7 +31,7 @@ class Endpoint { Endpoint(IMMDevice* endpoint); uint64_t getIndex(); void setIndex(uint64_t idx); - void setVolume(int channel, float volume); + void setVolume(NGuid* guid, int channel, float volume); /* float getLeftChannelVolume(); */ /* float getRightChannelVolume(); */ float getVolume(int channel); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 9df5822..12daf2f 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -26,10 +26,10 @@ uint64_t EndpointHandler::getIndex(){ /* * -1 for master volume */ -void EndpointHandler::setValue(int channel, int value){ +void EndpointHandler::setVolume(NGuid* guid, int channel, int value){ if (channel == ENDPOINT_MASTER_VOLUME) - ep->setVolume(channel, (float)value / 100); - else ep->setVolume(channel, (float)value / 100); + ep->setVolume(guid, channel, (float)value / 100); + else ep->setVolume(guid, channel, (float)value / 100); } void EndpointHandler::setMute(NGuid* guid, bool muted){ @@ -114,3 +114,10 @@ void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ epwMuteFunc f = &EndpointWidget::updateMute; std::invoke(f, endpointWidgets.at(idx), muted); } + +void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ + //int translatedNewVal = newVal * 100; + log_debugcpp("mainvolcallback float: " << newVal); + epwMainVolumeFunc f = &EndpointWidget::updateMainVolume; + std::invoke(f, endpointWidgets.at(idx), newVal); +} diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 16cab7c..0c4d279 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -36,7 +36,7 @@ public: float getVolume(int channel); bool getMute(); - void setValue(int channel, int value); + void setVolume(NGuid* guid, int channel, int value); void setMute(NGuid* guid, bool muted); ~EndpointHandler(); private: @@ -61,6 +61,7 @@ public: void reloadEndpointHandlers(); NGuid* getGuid(); void updateMuteCallback(uint64_t idx, bool muted); + void updateMainVolumeCallback(uint64_t idx, float newVal); //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 082766e..e1508fa 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -68,9 +68,9 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); - connect(mainSlider, &QSlider::valueChanged, [this](int newValue){this->eph->setValue(ENDPOINT_MASTER_VOLUME, newValue); }); - connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); - connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setValue(ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); + connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); + connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); + connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); /* * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); @@ -94,6 +94,20 @@ void EndpointWidget::updateMute(int checked){ this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); } +void EndpointWidget::updateMainVolume(int newValue){ + log_debugcpp("updateMainVolume slot"); + this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); +} + + +void EndpointWidget::updateMainVolume(float newValue){ + int newVal = newValue * 100; + if(this->mainSlider->value() != newVal) { + this->mainSlider->setValue(newVal); + } +} + + void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 3f2afb9..4ad1567 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -68,10 +68,12 @@ public: QSlider *rightChannelSlider = nullptr; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; + void updateMainVolume(float newValue); void updateMute(bool muted); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); public slots: + void updateMainVolume(int newValue); void updateMute(int checked); private: @@ -86,6 +88,7 @@ private: }; typedef void (EndpointWidget::*epwMuteFunc)(bool muted); +typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); class MainWindow : public QMainWindow { Q_OBJECT From cb81b493672c9990ab2253ce7cb754d76077865d Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Aug 2023 17:43:09 +0200 Subject: [PATCH 16/57] channels added to front programmatically --- src/back/backlasses.cpp | 15 +++++--- src/back/backlasses.h | 6 ++-- src/cont/contclasses.cpp | 4 +++ src/cont/contclasses.h | 2 ++ src/qt/qtclasses.cpp | 78 +++++++++++++++++++++++++++------------- src/qt/qtclasses.h | 8 +++-- 6 files changed, 78 insertions(+), 35 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 6500e1c..5f643f8 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -58,9 +58,11 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { * PAUDIO_VOLUME_NOTIFICATION_DATA->Release(); * } */ -Endpoint::Endpoint(IMMDevice* ep){ +Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; + this->idx = idx; if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { log_debugcpp("si"); }; + if (FAILED(endpointVolume->GetChannelCount(&channelCount))) log_debugcpp("get channel count fail"); //Obtaining friendly name: IPropertyStore creates PROPVARIANT per field // hr = endpointPtr->GetId(&endpointID); endpoint->OpenPropertyStore(STGM_READ, &properties); @@ -92,6 +94,10 @@ float Endpoint::getVolume(int channel){ return volume; } +uint32_t Endpoint::getChannelCount(){ + return (uint32_t)channelCount; +} + bool Endpoint::getMute(){ BOOL mut; @@ -122,6 +128,7 @@ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { if (channel == ENDPOINT_MASTER_VOLUME) { if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("si"); }; } else { + log_debugcpp("channel being updated: " << channel); if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("si"); }; } } @@ -165,9 +172,7 @@ void Overseer::initCOMLibrary() { GUID tempGuid; if(FAILED(CoCreateGuid(&tempGuid))) { log_debugcpp("Failed to obtain GUID: " ); }; - this->guid = GUIDToNGuid(&tempGuid); - //TODO: Release lpguid? //TODO: Uninitialize COM } @@ -188,8 +193,8 @@ void Overseer::reloadEndpoints() { for (unsigned int i = 0; i < numPlaybackEndpoints; i++){ IMMDevice *temp; if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; - Endpoint *endpoint = new Endpoint(temp); - endpoint->setIndex(i); + Endpoint *endpoint = new Endpoint(temp, i); + //endpoint->setIndex(i); this->playbackDevices.push_back(endpoint); //TODO: le porblemx std::cout << "ola" << std::endl; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 8e0b943..0e816a8 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -28,12 +28,11 @@ class EndpointCallback; class Endpoint { public: - Endpoint(IMMDevice* endpoint); + Endpoint(IMMDevice* endpoint, uint64_t idx); uint64_t getIndex(); void setIndex(uint64_t idx); void setVolume(NGuid* guid, int channel, float volume); - /* float getLeftChannelVolume(); */ - /* float getRightChannelVolume(); */ + uint32_t getChannelCount(); float getVolume(int channel); void setMute(NGuid* guid, bool muted); bool getMute(); @@ -43,6 +42,7 @@ class Endpoint { ~Endpoint(); private: + uint32_t channelCount; IMMDevice* endpoint; IAudioEndpointVolume *endpointVolume ; IPropertyStore *properties; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 12daf2f..86820c8 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -14,6 +14,10 @@ EndpointHandler::EndpointHandler(uint64_t idx) { ep->setCallback(epc); } +uint32_t EndpointHandler::getChannelCount(){ + return ep->getChannelCount(); +} + void EndpointHandler::setIndex(uint64_t idx){ this->idx = idx; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 0c4d279..f41549f 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -28,6 +28,8 @@ public: EndpointCallback *epc = nullptr; //std::wstring epName; + uint32_t getChannelCount(); + void setIndex(uint64_t idx); uint64_t getIndex(); void setVolume(int channel, float volume); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index e1508fa..d58d7e9 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -25,11 +25,11 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( muteButton = new QCheckBox(); mainLabel = new QLabel(QString::fromStdWString(eph->getName())); - leftChannelLabel = new QLabel("88"); - rightChannelLabel = new QLabel("77"); + //leftChannelLabel = new QLabel("88"); + //rightChannelLabel = new QLabel("77"); mainSlider = new QSlider(Qt::Horizontal); - leftChannelSlider = new QSlider(Qt::Horizontal); - rightChannelSlider = new QSlider(Qt::Horizontal); + //leftChannelSlider = new QSlider(Qt::Horizontal); + //rightChannelSlider = new QSlider(Qt::Horizontal); //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); mainSlider->setFocusPolicy(Qt::StrongFocus); @@ -37,23 +37,29 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); - leftChannelSlider->setTickInterval(5); - leftChannelSlider->setSingleStep(1); - leftChannelSlider->setRange(0,100); - rightChannelSlider->setTickInterval(5); - rightChannelSlider->setSingleStep(1); - rightChannelSlider->setRange(0,100); + + /* + * leftChannelSlider->setTickInterval(5); + * leftChannelSlider->setSingleStep(1); + * leftChannelSlider->setRange(0,100); + * rightChannelSlider->setTickInterval(5); + * rightChannelSlider->setSingleStep(1); + * rightChannelSlider->setRange(0,100); + */ muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; mainSlider->setValue((int)volume); volume = eph->getVolume(ENDPOINT_LEFT_CHANNEL_VOLUME) * 100; - leftChannelSlider->setValue((int)volume); - leftChannelLabel->setText(QString::number(volume)); - volume = eph->getVolume(ENDPOINT_RIGHT_CHANNEL_VOLUME) * 100; - rightChannelSlider->setValue((int)volume); - rightChannelLabel->setText(QString::number(volume)); + + /* + * leftChannelSlider->setValue((int)volume); + * leftChannelLabel->setText(QString::number(volume)); + * volume = eph->getVolume(ENDPOINT_RIGHT_CHANNEL_VOLUME) * 100; + * rightChannelSlider->setValue((int)volume); + * rightChannelLabel->setText(QString::number(volume)); + */ log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); mainMuteLayout = new QGridLayout(); @@ -61,20 +67,42 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( mainMuteLayout->addWidget(mainLabel, 0, 0); mainMuteLayout->addWidget(muteButton, 0, 1); layout->addWidget(mainSlider, 0, 1); - layout->addWidget(leftChannelSlider, 1, 0); - layout->addWidget(leftChannelLabel, 2, 0); - layout->addWidget(rightChannelSlider, 1, 1); - layout->addWidget(rightChannelLabel, 2, 1); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); + + /* + * layout->addWidget(leftChannelSlider, 1, 0); + * layout->addWidget(leftChannelLabel, 2, 0); + * layout->addWidget(rightChannelSlider, 1, 1); + * layout->addWidget(rightChannelLabel, 2, 1); + */ + /* + * layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); + * layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); + */ connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); - connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); - connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); /* - * connect(muteButton, &QPushButton::clicked, [this](bool clicked){ log_debugcpp("cliqui" << clicked << "cloqui"); this->eph->setMute(osh->getGuid()); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); }); + * connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); + * connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); */ + connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); + + for(uint32_t i = 0; i < eph->getChannelCount(); i++){ + QSlider* tmp = new QSlider(Qt::Horizontal); + QLabel* tmpLb = new QLabel(""); + tmp->setTickInterval(5); + tmp->setSingleStep(1); + tmp->setRange(0,100); + volume = eph->getVolume(i) * 100; + tmp->setValue((int) volume); + tmpLb->setText(QString::number(volume)); + this->channelSliders.push_back(tmp); + this->channelLabels.push_back(tmpLb); + layout->addWidget(tmp, 1, i); + layout->addWidget(tmpLb, 2, i); + connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); + } + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); log_debugcpp("ENDPOINT_WIDGETED"); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 4ad1567..aa134fb 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -64,8 +64,12 @@ public: QCheckBox *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; - QSlider *leftChannelSlider = nullptr; - QSlider *rightChannelSlider = nullptr; + std::vector channelSliders; + std::vector channelLabels; + /* + * QSlider *leftChannelSlider = nullptr; + * QSlider *rightChannelSlider = nullptr; + */ QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; void updateMainVolume(float newValue); From 4d9bae4c87672e15c86bcd929411ef8975e6dbc5 Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Aug 2023 18:29:24 +0200 Subject: [PATCH 17/57] wip channel callback --- src/back/backlasses.cpp | 4 ++++ src/cont/contclasses.cpp | 7 ++++++ src/cont/contclasses.h | 1 + src/global.h | 5 ++-- src/qt/qtclasses.cpp | 50 ++++++++-------------------------------- src/qt/qtclasses.h | 8 +++---- 6 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 5f643f8..928750e 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -49,6 +49,10 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); + log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); + for(UINT i = 0; i < eventData.nChannels; i++) { + osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)i, eventData.afChannelVolumes[i]); + } } return S_OK; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 86820c8..304547d 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -125,3 +125,10 @@ void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ epwMainVolumeFunc f = &EndpointWidget::updateMainVolume; std::invoke(f, endpointWidgets.at(idx), newVal); } + +void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ + //int translatedNewVal = newVal * 100; + log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); + epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; + std::invoke(f, endpointWidgets.at(idx), channel, newVal); +} diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index f41549f..59dd035 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -64,6 +64,7 @@ public: NGuid* getGuid(); void updateMuteCallback(uint64_t idx, bool muted); void updateMainVolumeCallback(uint64_t idx, float newVal); + void updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal); //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); diff --git a/src/global.h b/src/global.h index eaf2968..c4cf2af 100644 --- a/src/global.h +++ b/src/global.h @@ -7,10 +7,9 @@ #include "debug.h" -//TODO enum capullo #define ENDPOINT_MASTER_VOLUME -1 -#define ENDPOINT_LEFT_CHANNEL_VOLUME 0 -#define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 +/* #define ENDPOINT_LEFT_CHANNEL_VOLUME 0 */ +/* #define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 */ #define STRING_MUTE "Mute" #define STRING_UNMUTE "Unmute" diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index d58d7e9..79da832 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -25,41 +25,19 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( muteButton = new QCheckBox(); mainLabel = new QLabel(QString::fromStdWString(eph->getName())); - //leftChannelLabel = new QLabel("88"); - //rightChannelLabel = new QLabel("77"); mainSlider = new QSlider(Qt::Horizontal); - //leftChannelSlider = new QSlider(Qt::Horizontal); - //rightChannelSlider = new QSlider(Qt::Horizontal); - + //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); mainSlider->setFocusPolicy(Qt::StrongFocus); mainSlider->setTickPosition(QSlider::TicksBothSides); mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); - - /* - * leftChannelSlider->setTickInterval(5); - * leftChannelSlider->setSingleStep(1); - * leftChannelSlider->setRange(0,100); - * rightChannelSlider->setTickInterval(5); - * rightChannelSlider->setSingleStep(1); - * rightChannelSlider->setRange(0,100); - */ muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; mainSlider->setValue((int)volume); - volume = eph->getVolume(ENDPOINT_LEFT_CHANNEL_VOLUME) * 100; - - /* - * leftChannelSlider->setValue((int)volume); - * leftChannelLabel->setText(QString::number(volume)); - * volume = eph->getVolume(ENDPOINT_RIGHT_CHANNEL_VOLUME) * 100; - * rightChannelSlider->setValue((int)volume); - * rightChannelLabel->setText(QString::number(volume)); - */ log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); mainMuteLayout = new QGridLayout(); @@ -68,24 +46,9 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( mainMuteLayout->addWidget(muteButton, 0, 1); layout->addWidget(mainSlider, 0, 1); - /* - * layout->addWidget(leftChannelSlider, 1, 0); - * layout->addWidget(leftChannelLabel, 2, 0); - * layout->addWidget(rightChannelSlider, 1, 1); - * layout->addWidget(rightChannelLabel, 2, 1); - */ - /* - * layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); - * layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); - */ - connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); - /* - * connect(leftChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_LEFT_CHANNEL_VOLUME, newValue); this->leftChannelLabel->setText(QString::number(newValue)); }); - * connect(rightChannelSlider, &QSlider::valueChanged, [this](int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_RIGHT_CHANNEL_VOLUME, newValue); this->rightChannelLabel->setText(QString::number(newValue)); }); - */ connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); - + for(uint32_t i = 0; i < eph->getChannelCount(); i++){ QSlider* tmp = new QSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); @@ -101,6 +64,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( layout->addWidget(tmpLb, 2, i); connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); } + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); log_debugcpp("ENDPOINT_WIDGETED"); @@ -127,7 +91,6 @@ void EndpointWidget::updateMainVolume(int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); } - void EndpointWidget::updateMainVolume(float newValue){ int newVal = newValue * 100; if(this->mainSlider->value() != newVal) { @@ -135,6 +98,13 @@ void EndpointWidget::updateMainVolume(float newValue){ } } +void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ + int newVal = newValue * 100; + if(this->channelSliders.at(channel)->value() != newVal) { + this->channelSliders.at(channel)->setValue(newVal); + this->channelLabels.at(channel)->setText(QString::number((int)(newValue * 100))); + } +} void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index aa134fb..79acc74 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -66,16 +66,15 @@ public: QSlider *mainSlider = nullptr; std::vector channelSliders; std::vector channelLabels; - /* - * QSlider *leftChannelSlider = nullptr; - * QSlider *rightChannelSlider = nullptr; - */ QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; void updateMainVolume(float newValue); + void updateChannelVolume(uint32_t channel, float newValue); void updateMute(bool muted); + //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); + public slots: void updateMainVolume(int newValue); void updateMute(int checked); @@ -93,6 +92,7 @@ private: typedef void (EndpointWidget::*epwMuteFunc)(bool muted); typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); +typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); class MainWindow : public QMainWindow { Q_OBJECT From 7fb67cff3fb9d54787995683f56b6227d1c992bf Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 13 Aug 2023 18:31:34 +0200 Subject: [PATCH 18/57] forgot todo --- src/qt/qtclasses.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 79da832..cdcb988 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -62,6 +62,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( this->channelLabels.push_back(tmpLb); layout->addWidget(tmp, 1, i); layout->addWidget(tmpLb, 2, i); + //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); } From e30ed58c0800f69ff61890d0db65d99aa9be70e5 Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 14 Aug 2023 16:10:35 +0200 Subject: [PATCH 19/57] broken: play dj outside, get it stuck --- src/back/backlasses.cpp | 30 ++++++++++++++++++++++-------- src/cont/contclasses.cpp | 10 +++++++++- src/cont/contclasses.h | 3 ++- src/qt/qtclasses.cpp | 24 ++++++++++++++++++++++-- src/qt/qtclasses.h | 3 +++ 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 928750e..5051b22 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -38,8 +38,16 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - + + float extraChannelVol[pNotify->nChannels]; + bool multiChannel = false; AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; + if(pNotify->nChannels > 1) { + multiChannel = true; + for (UINT i = 0; i < pNotify->nChannels; i++){ + extraChannelVol[i] = pNotify->afChannelVolumes[i]; + } + } NGuid* guid = osh->getGuid(); if (memcmp(guid, &eventData, sizeof(*guid)) == 0) { @@ -47,12 +55,18 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { } else { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); + osh->toggleFrontEvents(this->ep->getIndex(), false); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); - for(UINT i = 0; i < eventData.nChannels; i++) { - osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)i, eventData.afChannelVolumes[i]); - } + + if(multiChannel) + for(UINT i = 0; i < eventData.nChannels; i++) { + osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); + } + else + osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); + osh->toggleFrontEvents(this->ep->getIndex(), true); } return S_OK; @@ -130,10 +144,10 @@ bool Endpoint::getMute(){ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { GUID tempMsGuid = NGuidToGUID(guid); if (channel == ENDPOINT_MASTER_VOLUME) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("si"); }; + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("MASTER VOLUME FAILED"); }; } else { - log_debugcpp("channel being updated: " << channel); - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("si"); }; + log_debugcpp("Channel being updated: " << channel); + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("CHANNEL "<< channel <<" VOLUME FAILED"); }; } } @@ -144,7 +158,7 @@ void Endpoint::setMute(NGuid* guid, bool muted) { //log_debugcpp("translate to BOOL as " << mut); //TODO: use new funcs GUID tempMsGuid = NGuidToGUID(guid); - if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_debugcpp("si"); }; + if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_debugcpp("MUTE FAILED"); }; } void Endpoint::setCallback(EndpointCallback *epc){ diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 304547d..fb7a6c3 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -100,7 +100,6 @@ void OverseerHandler::reloadEndpointHandlers(){ //setEndpointHandlers(ephs); } - NGuid* OverseerHandler::getGuid() { return this->os.getGuid(); } @@ -128,7 +127,16 @@ void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ //int translatedNewVal = newVal * 100; + log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; std::invoke(f, endpointWidgets.at(idx), channel, newVal); + + //TODO: Soy retrasado + //endpointWidgets.at(idx)->updateChannelVolume(channel, newVal); +} + +void OverseerHandler::toggleFrontEvents(uint64_t idx, bool active) { + epwToggleFrontFunc f = &EndpointWidget::toggleFrontEvents; + std::invoke(f, endpointWidgets.at(idx), active); } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 59dd035..da58587 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -40,6 +40,7 @@ public: void setVolume(NGuid* guid, int channel, int value); void setMute(NGuid* guid, bool muted); + ~EndpointHandler(); private: uint64_t idx; @@ -65,7 +66,7 @@ public: void updateMuteCallback(uint64_t idx, bool muted); void updateMainVolumeCallback(uint64_t idx, float newVal); void updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal); - + void toggleFrontEvents(uint64_t idx, bool active); //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); //static Overseer* getOverseer(); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index cdcb988..f6e01b4 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -73,9 +73,12 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( void EndpointWidget::updateMute(bool muted){ log_debugcpp("cliqui callboqui cloqui"); + //TODO: Here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. + //this->muteButton->blockSignals(true); this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(eph->getMute() ? true : false); - this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + //this->muteButton->blockSignals(false); } void EndpointWidget::updateMute(int checked){ @@ -88,25 +91,42 @@ void EndpointWidget::updateMute(int checked){ } void EndpointWidget::updateMainVolume(int newValue){ - log_debugcpp("updateMainVolume slot"); + log_debugcpp("updateMainVolume slot."); this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); } void EndpointWidget::updateMainVolume(float newValue){ int newVal = newValue * 100; + log_debugcpp("mainvolcallback int: " << newVal); + //this->mainSlider->blockSignals(true); + //TODO: Above if(this->mainSlider->value() != newVal) { this->mainSlider->setValue(newVal); } + //this->mainSlider->blockSignals(false); } void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ int newVal = newValue * 100; + log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); + //TODO: Above + //this->channelSliders.at(channel)->blockSignals(true); if(this->channelSliders.at(channel)->value() != newVal) { this->channelSliders.at(channel)->setValue(newVal); this->channelLabels.at(channel)->setText(QString::number((int)(newValue * 100))); } + //this->channelSliders.at(channel)->blockSignals(false); } +void EndpointWidget::toggleFrontEvents(bool active){ + this->muteButton->blockSignals(active); + this->mainSlider->blockSignals(active); + for(uint32_t i = 0; i < this->channelSliders.size(); i++){ + this->channelSliders.at(i)->blockSignals(active); + } +} + + void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 79acc74..21bdf0d 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -71,6 +71,7 @@ public: void updateMainVolume(float newValue); void updateChannelVolume(uint32_t channel, float newValue); void updateMute(bool muted); + void toggleFrontEvents(bool active); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); @@ -93,6 +94,8 @@ private: typedef void (EndpointWidget::*epwMuteFunc)(bool muted); typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); +typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); + class MainWindow : public QMainWindow { Q_OBJECT From 33330419a92dd8ffeaf020d4163e94a0b5dce9e9 Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 14 Aug 2023 16:24:07 +0200 Subject: [PATCH 20/57] broken: fixed event toggling order --- src/back/backlasses.cpp | 4 ++-- src/cont/contclasses.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 5051b22..5030f11 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -55,7 +55,7 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { } else { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); - osh->toggleFrontEvents(this->ep->getIndex(), false); + osh->toggleFrontEvents(this->ep->getIndex(), true); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); @@ -66,7 +66,7 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { } else osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); - osh->toggleFrontEvents(this->ep->getIndex(), true); + osh->toggleFrontEvents(this->ep->getIndex(), false); } return S_OK; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index fb7a6c3..b7db7dc 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -128,9 +128,11 @@ void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ //int translatedNewVal = newVal * 100; + log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; std::invoke(f, endpointWidgets.at(idx), channel, newVal); + //TODO: Soy retrasado //endpointWidgets.at(idx)->updateChannelVolume(channel, newVal); From 60aff9891f7cb9671480de769b32a1be095169d7 Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 14 Aug 2023 18:00:59 +0200 Subject: [PATCH 21/57] added pdb generation, fixed unintended back call --- qtest.pro | 6 +++--- src/cont/contclasses.cpp | 4 +--- src/qt/qtclasses.cpp | 2 +- src/qt/qtclasses.h | 1 - 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/qtest.pro b/qtest.pro index d2ac80f..511e78a 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,6 +1,6 @@ -QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -QMAKE_LINKER += clang++ -QMAKE_LFLAGS += -v +QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview +#QMAKE_LINK += clang++ +QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview -Wl,-pdb= -v DEFINES += DEBUG CONFIG += debug console QT += widgets diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index b7db7dc..7bca344 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -127,12 +127,10 @@ void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ //int translatedNewVal = newVal * 100; - - + log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; std::invoke(f, endpointWidgets.at(idx), channel, newVal); - //TODO: Soy retrasado //endpointWidgets.at(idx)->updateChannelVolume(channel, newVal); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index f6e01b4..a5d0b8a 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -75,7 +75,7 @@ void EndpointWidget::updateMute(bool muted){ log_debugcpp("cliqui callboqui cloqui"); //TODO: Here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. //this->muteButton->blockSignals(true); - this->eph->setMute(osh->getGuid(), muted); + //this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(eph->getMute() ? true : false); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); //this->muteButton->blockSignals(false); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 21bdf0d..dc1a82a 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -96,7 +96,6 @@ typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); - class MainWindow : public QMainWindow { Q_OBJECT //QWidget *centralWidget; From 81f6cb32c857f370f70c7e5831e5b7635671a17d Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 14 Aug 2023 18:23:49 +0200 Subject: [PATCH 22/57] changed invoke instances with call-to-mem-fun macro --- src/cont/contclasses.cpp | 13 ++++++++----- src/cont/contclasses.h | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 7bca344..8281bb8 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -115,14 +115,16 @@ void OverseerHandler::setEndpointWidgets(std::vector ews){ void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ epwMuteFunc f = &EndpointWidget::updateMute; - std::invoke(f, endpointWidgets.at(idx), muted); + pinvoke_mem_fn(endpointWidgets.at(idx),f)(muted); + //std::invoke(f, endpointWidgets.at(idx), muted); } void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ //int translatedNewVal = newVal * 100; log_debugcpp("mainvolcallback float: " << newVal); epwMainVolumeFunc f = &EndpointWidget::updateMainVolume; - std::invoke(f, endpointWidgets.at(idx), newVal); + pinvoke_mem_fn(endpointWidgets.at(idx),f)(newVal); + //std::invoke(f, endpointWidgets.at(idx), newVal); } void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ @@ -130,13 +132,14 @@ void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; - std::invoke(f, endpointWidgets.at(idx), channel, newVal); - + //std::invoke(f, endpointWidgets.at(idx), channel, newVal); + pinvoke_mem_fn(endpointWidgets.at(idx),f)(channel, newVal); //TODO: Soy retrasado //endpointWidgets.at(idx)->updateChannelVolume(channel, newVal); } void OverseerHandler::toggleFrontEvents(uint64_t idx, bool active) { epwToggleFrontFunc f = &EndpointWidget::toggleFrontEvents; - std::invoke(f, endpointWidgets.at(idx), active); + pinvoke_mem_fn(endpointWidgets.at(idx),f)(active); + //std::invoke(f, , active); } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index da58587..5714a79 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,4 +1,6 @@ #pragma once +#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) +#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) /* #ifndef QTBLESSED */ /* //#define Q_OBJECT */ From 5bb37eb9fd752c07dfeb174691b998977099d166 Mon Sep 17 00:00:00 2001 From: Phireh Date: Mon, 14 Aug 2023 20:10:59 +0200 Subject: [PATCH 23/57] Remove frontend header from contclasses --- src/back/backlasses.cpp | 2 -- src/cont/contclasses.cpp | 44 ++++++++++++++++------------------------ src/cont/contclasses.h | 16 ++++++++++----- src/qt/qtclasses.cpp | 39 ++++++++++++++++++++++++++--------- src/qt/qtclasses.h | 1 + 5 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 5030f11..32c7940 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -55,7 +55,6 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { } else { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); - osh->toggleFrontEvents(this->ep->getIndex(), true); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); @@ -66,7 +65,6 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { } else osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); - osh->toggleFrontEvents(this->ep->getIndex(), false); } return S_OK; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 8281bb8..36b913d 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -1,5 +1,4 @@ #include "backlasses.h" -#include "qtclasses.h" #include "contclasses.h" //TODO: pragma once @@ -64,12 +63,6 @@ std::vector OverseerHandler::getPlaybackEndpoints() { return this->os.getPlaybackEndpoints(); } -/* - * Overseer* OverseerHandler::getOverseer(){ - * return &os; - * } - */ - std::vector OverseerHandler::getEndpointHandlers(){ return endpointHandlers; @@ -114,32 +107,29 @@ void OverseerHandler::setEndpointWidgets(std::vector ews){ } void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ - epwMuteFunc f = &EndpointWidget::updateMute; - pinvoke_mem_fn(endpointWidgets.at(idx),f)(muted); - //std::invoke(f, endpointWidgets.at(idx), muted); + updateFrontMuteCallback(idx, muted); } void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ - //int translatedNewVal = newVal * 100; log_debugcpp("mainvolcallback float: " << newVal); - epwMainVolumeFunc f = &EndpointWidget::updateMainVolume; - pinvoke_mem_fn(endpointWidgets.at(idx),f)(newVal); - //std::invoke(f, endpointWidgets.at(idx), newVal); + updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); +} + +void OverseerHandler::setFrontVolumeCallback(std::function f) { + this->updateFrontVolumeCallback = f; +} + +void OverseerHandler::setFrontMuteCallback(std::function f) { + this->updateFrontMuteCallback = f; } void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ - //int translatedNewVal = newVal * 100; + log_debugcpp("channel: " << channel << " volcallback float: " << newVal); - log_debugcpp("chanel: " << channel << " volcallback float: " << newVal); - epwChannelVolumeFunc f = &EndpointWidget::updateChannelVolume; - //std::invoke(f, endpointWidgets.at(idx), channel, newVal); - pinvoke_mem_fn(endpointWidgets.at(idx),f)(channel, newVal); - //TODO: Soy retrasado - //endpointWidgets.at(idx)->updateChannelVolume(channel, newVal); -} - -void OverseerHandler::toggleFrontEvents(uint64_t idx, bool active) { - epwToggleFrontFunc f = &EndpointWidget::toggleFrontEvents; - pinvoke_mem_fn(endpointWidgets.at(idx),f)(active); - //std::invoke(f, , active); + // convert channel to bitmask + int i = 0; + while (i++); + uint32_t mask = (1 << i); + + updateFrontVolumeCallback(idx, mask, newVal); } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 5714a79..bb47131 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -9,11 +9,16 @@ /* #endif */ class EndpointWidget; - class Endpoint; class EndpointCallback; class Overseer; +enum AudioChannel { + CHANNEL_LEFT = (1 << 0), + CHANNEL_RIGHT = (1 << 1), + CHANNEL_MAIN = ~0, +}; + struct NGuid { uint32_t data1; uint16_t data2; @@ -66,14 +71,15 @@ public: void reloadEndpointHandlers(); NGuid* getGuid(); void updateMuteCallback(uint64_t idx, bool muted); + void setFrontMuteCallback(std::function f); + void setFrontVolumeCallback(std::function f); void updateMainVolumeCallback(uint64_t idx, float newVal); void updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal); - void toggleFrontEvents(uint64_t idx, bool active); - //void parseExternalEndpointCallback(EndpointCallback *epc, PAUDIO_VOLUME_NOTIFICATION_DATA pNotify); - //static Overseer* getOverseer(); private: static Overseer os; std::vector endpointHandlers; - std::vector endpointWidgets; + std::vector endpointWidgets; + std::function updateFrontVolumeCallback = {}; + std::function updateFrontMuteCallback = {}; }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index a5d0b8a..24287e9 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -74,11 +74,11 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( void EndpointWidget::updateMute(bool muted){ log_debugcpp("cliqui callboqui cloqui"); //TODO: Here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. - //this->muteButton->blockSignals(true); + this->muteButton->blockSignals(true); //this->eph->setMute(osh->getGuid(), muted); - this->muteButton->setChecked(eph->getMute() ? true : false); + this->muteButton->setChecked(muted); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - //this->muteButton->blockSignals(false); + this->muteButton->blockSignals(false); } void EndpointWidget::updateMute(int checked){ @@ -107,14 +107,23 @@ void EndpointWidget::updateMainVolume(float newValue){ } void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ - int newVal = newValue * 100; - log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); - //TODO: Above - //this->channelSliders.at(channel)->blockSignals(true); - if(this->channelSliders.at(channel)->value() != newVal) { - this->channelSliders.at(channel)->setValue(newVal); - this->channelLabels.at(channel)->setText(QString::number((int)(newValue * 100))); + this->blockSignals(true); + if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + updateMainVolume(newValue); + } else { + int newVal = newValue * 100; + log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); + //TODO: Above + //this->channelSliders.at(channel)->blockSignals(true); + + for (size_t i = 0; i < sizeof(uint32_t) * 8; ++i) { + if ((channel << i & 1) && this->channelSliders.at(channel)->value() != newVal) { + this->channelSliders.at(channel)->setValue(newVal); + this->channelLabels.at(channel)->setText(QString::number((int)(newValue * 100))); + } + } } + this->blockSignals(false); //this->channelSliders.at(channel)->blockSignals(false); } @@ -160,8 +169,18 @@ void MainWindow::reloadEndpointWidgets() { } osh->setEndpointWidgets(ews); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); + + osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { + if (device < ews.size()) + ews[device]->updateChannelVolume(channel, value); + }); + osh->setFrontMuteCallback([this](uint64_t device, bool muted) { + if (device < ews.size()) + ews[device]->updateMute(muted); + }); } + //#include "qtclosemwh.h" /* diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index dc1a82a..788e279 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -103,6 +103,7 @@ class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr); void reloadEndpointWidgets(); + //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); From 9b1c251a3c7d2ffb8f9c6158ba07e0e6fb43445a Mon Sep 17 00:00:00 2001 From: Phireh Date: Mon, 14 Aug 2023 22:18:24 +0200 Subject: [PATCH 24/57] Correctly update channel sliders --- src/cont/contclasses.cpp | 3 ++- src/qt/qtclasses.cpp | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 36b913d..3727fd4 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -128,7 +128,8 @@ void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel // convert channel to bitmask int i = 0; - while (i++); + while (i < channel) + i++; uint32_t mask = (1 << i); updateFrontVolumeCallback(idx, mask, newVal); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 24287e9..bebddf2 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -108,23 +108,24 @@ void EndpointWidget::updateMainVolume(float newValue){ void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ this->blockSignals(true); - if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) updateMainVolume(newValue); - } else { - int newVal = newValue * 100; - log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); - //TODO: Above - //this->channelSliders.at(channel)->blockSignals(true); + + int newVal = newValue * 100; + log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); - for (size_t i = 0; i < sizeof(uint32_t) * 8; ++i) { - if ((channel << i & 1) && this->channelSliders.at(channel)->value() != newVal) { - this->channelSliders.at(channel)->setValue(newVal); - this->channelLabels.at(channel)->setText(QString::number((int)(newValue * 100))); - } + for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { + if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { + this->channelSliders.at(i)->blockSignals(true); + + this->channelSliders.at(i)->setValue(newVal); + this->channelLabels.at(i)->setText(QString::number((int)(newValue * 100))); + + this->channelSliders.at(i)->blockSignals(false); } } + this->blockSignals(false); - //this->channelSliders.at(channel)->blockSignals(false); } void EndpointWidget::toggleFrontEvents(bool active){ From 729f01a1892bc28665356706c16bdfaad3784a1b Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 15 Aug 2023 17:03:23 +0200 Subject: [PATCH 25/57] code cleanup; channels callback coalesced again --- src/back/backlasses.cpp | 6 +++--- src/cont/contclasses.cpp | 39 +++++++++++++++++++------------------- src/cont/contclasses.h | 11 +++++------ src/debug.h | 8 +++++++- src/qt/qtclasses.cpp | 41 +++++++++++++++++++++------------------- src/qt/qtclasses.h | 8 +------- 6 files changed, 58 insertions(+), 55 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 32c7940..ef827dd 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -56,15 +56,15 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { log_debugcpp("Onnanokotify says Stored: " << guid->data1); log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); - osh->updateMainVolumeCallback(this->ep->getIndex(), eventData.fMasterVolume); + osh->updateVolumeCallback(this->ep->getIndex(), AudioChannel::CHANNEL_MAIN ,eventData.fMasterVolume); log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); if(multiChannel) for(UINT i = 0; i < eventData.nChannels; i++) { - osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); + osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); } else - osh->updateChannelVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); + osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); } return S_OK; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 3727fd4..679f921 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -6,8 +6,8 @@ Overseer OverseerHandler::os; EndpointHandler::EndpointHandler(uint64_t idx) { - std::vector endpoints = osh->getPlaybackEndpoints(); - this->ep = endpoints.at(idx); + //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); + this->ep = osh->getPlaybackEndpoints().at(idx); epc = new EndpointCallback(ep); //epName = ep->getName(); ep->setCallback(epc); @@ -25,7 +25,6 @@ uint64_t EndpointHandler::getIndex(){ return idx; } - /* * -1 for master volume */ @@ -101,20 +100,6 @@ void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; } - -void OverseerHandler::setEndpointWidgets(std::vector ews){ - this->endpointWidgets = ews; -} - -void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ - updateFrontMuteCallback(idx, muted); -} - -void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ - log_debugcpp("mainvolcallback float: " << newVal); - updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); -} - void OverseerHandler::setFrontVolumeCallback(std::function f) { this->updateFrontVolumeCallback = f; } @@ -123,11 +108,27 @@ void OverseerHandler::setFrontMuteCallback(std::function f this->updateFrontMuteCallback = f; } -void OverseerHandler::updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ +void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ + updateFrontMuteCallback(idx, muted); +} + +/* + * void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ + * + * } + */ + +void OverseerHandler::updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ + if (channel == AudioChannel::CHANNEL_MAIN) { + log_debugcpp("mainvolcallback float: " << newVal); + updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); + return; + } + log_debugcpp("channel: " << channel << " volcallback float: " << newVal); // convert channel to bitmask - int i = 0; + uint32_t i = 0; while (i < channel) i++; uint32_t mask = (1 << i); diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index bb47131..320564f 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -64,7 +64,6 @@ class OverseerHandler { public: //OverseerHandler(); void setEndpointHandlers(std::vector ephs); - void setEndpointWidgets(std::vector ews); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); @@ -73,13 +72,13 @@ public: void updateMuteCallback(uint64_t idx, bool muted); void setFrontMuteCallback(std::function f); void setFrontVolumeCallback(std::function f); - void updateMainVolumeCallback(uint64_t idx, float newVal); - void updateChannelVolumeCallback(uint64_t idx, uint32_t channel, float newVal); + //void updateMainVolumeCallback(uint64_t idx, float newVal); + void updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal); private: static Overseer os; std::vector endpointHandlers; - std::vector endpointWidgets; - std::function updateFrontVolumeCallback = {}; - std::function updateFrontMuteCallback = {}; + std::function updateFrontVolumeCallback; + std::function updateFrontMuteCallback; + }; diff --git a/src/debug.h b/src/debug.h index 78cb19c..e80893f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -16,8 +16,14 @@ std::bitset varToBitset(T info) { #else #define log_debugcpp(str) -#define print_as_binary(len, info) +#define print_as_binary(len, type, info) #endif +/* Here as a quick reference, in case smthn similar is needed again */ +/* typedef void (EndpointWidget::*epwMuteFunc)(bool muted); */ +/* typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); */ +/* typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); */ +/* typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); */ + diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index bebddf2..9a446ee 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -73,12 +73,14 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( void EndpointWidget::updateMute(bool muted){ log_debugcpp("cliqui callboqui cloqui"); - //TODO: Here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. - this->muteButton->blockSignals(true); + //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. + //this->blockSignals(true); + //this->muteButton->blockSignals(true); //this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(muted); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - this->muteButton->blockSignals(false); + //this->muteButton->blockSignals(false); + //this->blockSignals(false); } void EndpointWidget::updateMute(int checked){ @@ -96,18 +98,20 @@ void EndpointWidget::updateMainVolume(int newValue){ } void EndpointWidget::updateMainVolume(float newValue){ + //this->blockSignals(true); int newVal = newValue * 100; log_debugcpp("mainvolcallback int: " << newVal); //this->mainSlider->blockSignals(true); - //TODO: Above + if(this->mainSlider->value() != newVal) { this->mainSlider->setValue(newVal); } //this->mainSlider->blockSignals(false); + //this->blockSignals(false); } void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ - this->blockSignals(true); + //this->blockSignals(true); if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) updateMainVolume(newValue); @@ -116,25 +120,27 @@ void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { - this->channelSliders.at(i)->blockSignals(true); + //this->channelSliders.at(i)->blockSignals(true); this->channelSliders.at(i)->setValue(newVal); this->channelLabels.at(i)->setText(QString::number((int)(newValue * 100))); - this->channelSliders.at(i)->blockSignals(false); + //this->channelSliders.at(i)->blockSignals(false); } } - this->blockSignals(false); + //this->blockSignals(false); } -void EndpointWidget::toggleFrontEvents(bool active){ - this->muteButton->blockSignals(active); - this->mainSlider->blockSignals(active); - for(uint32_t i = 0; i < this->channelSliders.size(); i++){ - this->channelSliders.at(i)->blockSignals(active); - } -} +/* + * void EndpointWidget::toggleFrontEvents(bool active){ + * this->muteButton->blockSignals(active); + * this->mainSlider->blockSignals(active); + * for(uint32_t i = 0; i < this->channelSliders.size(); i++){ + * this->channelSliders.at(i)->blockSignals(active); + * } + * } + */ void EndpointWidget::setIndex(uint64_t idx){ @@ -168,7 +174,7 @@ void MainWindow::reloadEndpointWidgets() { ews.push_back(epw); layout->addWidget(epw, i, 0); } - osh->setEndpointWidgets(ews); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { @@ -181,9 +187,6 @@ void MainWindow::reloadEndpointWidgets() { }); } - -//#include "qtclosemwh.h" - /* * void MainWindow::setPlotButton() { * button = new QPushButton("push"), diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 788e279..4482d23 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -71,7 +71,7 @@ public: void updateMainVolume(float newValue); void updateChannelVolume(uint32_t channel, float newValue); void updateMute(bool muted); - void toggleFrontEvents(bool active); + //void toggleFrontEvents(bool active); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); @@ -84,18 +84,12 @@ private: uint64_t idx; //std::vector *ephs; //std::vector *sliders; - //signals: //void valueChanged(int value); }; -typedef void (EndpointWidget::*epwMuteFunc)(bool muted); -typedef void (EndpointWidget::*epwMainVolumeFunc)(float newValue); -typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); -typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); - class MainWindow : public QMainWindow { Q_OBJECT //QWidget *centralWidget; From 966cf91a230450ffcd6a80c015b5386356ee04f9 Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 15 Aug 2023 18:06:55 +0200 Subject: [PATCH 26/57] more code cleanup; changed define for channel enum --- src/back/backlasses.cpp | 4 ++-- src/cont/contclasses.cpp | 7 ++++--- src/qt/qtclasses.cpp | 33 ++++++++++++++------------------- src/qt/qtclasses.h | 4 ++-- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index ef827dd..5269c29 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -141,10 +141,10 @@ bool Endpoint::getMute(){ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { GUID tempMsGuid = NGuidToGUID(guid); - if (channel == ENDPOINT_MASTER_VOLUME) { + if (channel == AudioChannel::CHANNEL_MAIN) { if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("MASTER VOLUME FAILED"); }; } else { - log_debugcpp("Channel being updated: " << channel); + log_debugcpp("Windows: Channel being updated: " << channel); if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("CHANNEL "<< channel <<" VOLUME FAILED"); }; } } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 679f921..381550b 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -119,19 +119,20 @@ void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ */ void OverseerHandler::updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ - if (channel == AudioChannel::CHANNEL_MAIN) { + if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { log_debugcpp("mainvolcallback float: " << newVal); updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); return; } - log_debugcpp("channel: " << channel << " volcallback float: " << newVal); - + // convert channel to bitmask uint32_t i = 0; while (i < channel) i++; uint32_t mask = (1 << i); + + log_debugcpp("Back->Cont Channel: " << mask << " volcallback float: " << newVal); updateFrontVolumeCallback(idx, mask, newVal); } diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 9a446ee..155cd90 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -76,6 +76,7 @@ void EndpointWidget::updateMute(bool muted){ //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. //this->blockSignals(true); //this->muteButton->blockSignals(true); + //this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(muted); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); @@ -97,26 +98,20 @@ void EndpointWidget::updateMainVolume(int newValue){ this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); } -void EndpointWidget::updateMainVolume(float newValue){ - //this->blockSignals(true); - int newVal = newValue * 100; - log_debugcpp("mainvolcallback int: " << newVal); - //this->mainSlider->blockSignals(true); - - if(this->mainSlider->value() != newVal) { - this->mainSlider->setValue(newVal); - } - //this->mainSlider->blockSignals(false); - //this->blockSignals(false); -} - -void EndpointWidget::updateChannelVolume(uint32_t channel, float newValue){ +void EndpointWidget::updateVolume(uint32_t channel, float newValue){ //this->blockSignals(true); - if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) - updateMainVolume(newValue); - int newVal = newValue * 100; - log_debugcpp("chanel: " << channel << " volcallback int: " << newVal); + if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + log_debugcpp("mainvolcallback int: " << newVal); + //this->mainSlider->blockSignals(true); + + if(this->mainSlider->value() != newVal) { + this->mainSlider->setValue(newVal); + } + return; + } + + log_debugcpp("Cont->Front Channel:: " << channel << " volcallback int: " << newVal); for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { @@ -179,7 +174,7 @@ void MainWindow::reloadEndpointWidgets() { osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { if (device < ews.size()) - ews[device]->updateChannelVolume(channel, value); + ews[device]->updateVolume(channel, value); }); osh->setFrontMuteCallback([this](uint64_t device, bool muted) { if (device < ews.size()) diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 4482d23..c5eee14 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -68,8 +68,8 @@ public: std::vector channelLabels; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; - void updateMainVolume(float newValue); - void updateChannelVolume(uint32_t channel, float newValue); + //void updateMainVolume(float newValue); + void updateVolume(uint32_t channel, float newValue); void updateMute(bool muted); //void toggleFrontEvents(bool active); From d1f0bcaf26f999ec044f364948a1546b11d64cec Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 16 Aug 2023 19:50:58 +0200 Subject: [PATCH 27/57] back to front now works without hangs? --- src/back/backlasses.cpp | 77 ++++++++++++++++++++++++---------------- src/cont/contclasses.cpp | 56 ++++++++++++++++++----------- src/cont/contclasses.h | 27 ++++++++++---- src/qt/qtclasses.cpp | 77 +++++++++++++++++++++++++++++++--------- src/qt/qtclasses.h | 3 +- 5 files changed, 164 insertions(+), 76 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 5269c29..28b8822 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -38,34 +38,46 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; + + /* + * float extraChannelVol[pNotify->nChannels]; + * for (UINT i = 0; i < pNotify->nChannels; i++){ + * AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; + * extraChannelVol[i] = pNotify->afChannelVolumes[i]; + * } + */ - float extraChannelVol[pNotify->nChannels]; - bool multiChannel = false; - AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; - if(pNotify->nChannels > 1) { - multiChannel = true; - for (UINT i = 0; i < pNotify->nChannels; i++){ - extraChannelVol[i] = pNotify->afChannelVolumes[i]; - } - } - NGuid* guid = osh->getGuid(); - - if (memcmp(guid, &eventData, sizeof(*guid)) == 0) { - log_debugcpp("Onnanokotify says You Shall Not Update Thy Interface."); - } else { - log_debugcpp("Onnanokotify says Stored: " << guid->data1); - log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); - osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); - osh->updateVolumeCallback(this->ep->getIndex(), AudioChannel::CHANNEL_MAIN ,eventData.fMasterVolume); - log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); + // osh->callbackInfo[this->ep->getIndex()]->caller = (NGuid)pNotify->guidEventContext; + memcpy(osh->callbackInfo[this->ep->getIndex()]->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - if(multiChannel) - for(UINT i = 0; i < eventData.nChannels; i++) { - osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); - } - else - osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)0, pNotify->afChannelVolumes[0]); - } + osh->callbackInfo[this->ep->getIndex()]->muted = pNotify->bMuted; + osh->callbackInfo[this->ep->getIndex()]->mainVolume = pNotify->fMasterVolume; + osh->callbackInfo[this->ep->getIndex()]->channels = pNotify->nChannels; + + UINT i = 0; + do { + osh->callbackInfo[this->ep->getIndex()]->channelVolumes[i] = pNotify->afChannelVolumes[i]; + } while(i++ < pNotify->nChannels); + + //osh->receiveBackEndpointCallback(this->ep->getIndex()); + + /* NGuid* guid = osh->getGuid(); + * //AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + * if (memcmp(guid, &eventData.guidEventContext, sizeof(*guid)) == 0) { + * //log_debugcpp("Onnanokotify says You Shall Not Update Thy Interface."); + * } else { + * //log_debugcpp("Onnanokotify says Stored: " << guid->data1); + * //log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); + * osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); + * osh->updateVolumeCallback(this->ep->getIndex(), AudioChannel::CHANNEL_MAIN ,eventData.fMasterVolume); + * //log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); + * + * + * for(UINT i = 0; i < eventData.nChannels; i++) { + * osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); + * } + * } + */ return S_OK; } @@ -118,9 +130,10 @@ uint32_t Endpoint::getChannelCount(){ bool Endpoint::getMute(){ BOOL mut; if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - log_debugcpp("back BOOL is " << mut); + //TODO: mutex test + //log_debugcpp("back BOOL is " << mut); bool mute = (bool)mut; - log_debugcpp("translate to bool " << mute); + //log_debugcpp("translate to bool " << mute); return mute; } @@ -140,12 +153,14 @@ bool Endpoint::getMute(){ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { + //TODO: mutex test GUID tempMsGuid = NGuidToGUID(guid); if (channel == AudioChannel::CHANNEL_MAIN) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { log_debugcpp("MASTER VOLUME FAILED"); }; + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { //log_debugcpp("MASTER VOLUME FAILED"); + }; } else { - log_debugcpp("Windows: Channel being updated: " << channel); - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { log_debugcpp("CHANNEL "<< channel <<" VOLUME FAILED"); }; + //log_debugcpp("Windows: Channel being updated: " << channel); + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { /* log_debugcpp("CHANNEL "<< channel <<" VOLUME FAILED"); */ }; } } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 381550b..20ca757 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -36,7 +36,8 @@ void EndpointHandler::setVolume(NGuid* guid, int channel, int value){ void EndpointHandler::setMute(NGuid* guid, bool muted){ //Qt momento, de ahi el param? - log_debugcpp("kinda handling the muting tbh"); + //TODO: mutex test + //log_debugcpp("kinda handling the muting tbh"); ep->setMute(guid, muted); } @@ -108,9 +109,11 @@ void OverseerHandler::setFrontMuteCallback(std::function f this->updateFrontMuteCallback = f; } -void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ - updateFrontMuteCallback(idx, muted); -} +/* + * void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ + * updateFrontMuteCallback(idx, muted); + * } + */ /* * void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ @@ -118,21 +121,32 @@ void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ * } */ -void OverseerHandler::updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ - if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { - log_debugcpp("mainvolcallback float: " << newVal); - updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); - return; - } - - - // convert channel to bitmask - uint32_t i = 0; - while (i < channel) - i++; - uint32_t mask = (1 << i); +/* + * void OverseerHandler::updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ + * if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + * //TODO: mutex test + * //log_debugcpp("mainvolcallback float: " << newVal); + * updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); + * return; + * } + * + * + * // convert channel to bitmask + * uint32_t i = 0; + * while (i < channel) + * i++; + * uint32_t mask = (1 << i); + * //TODO: Mutex test + * //log_debugcpp("Back->Cont Channel: " << mask << " volcallback float: " << newVal); + * + * updateFrontVolumeCallback(idx, mask, newVal); + * } + */ - log_debugcpp("Back->Cont Channel: " << mask << " volcallback float: " << newVal); - - updateFrontVolumeCallback(idx, mask, newVal); -} +/* + * void OverseerHandler::receiveBackEndpointCallback(uint64_t index) { + * if (memcmp(os.getGuid(), callbackInfo.caller, sizeof(*guid)) != 0) + * updateFrontCallback(uint64_t index, &callbackInfo); + * + * } + */ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 320564f..2ed4578 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -20,10 +20,18 @@ enum AudioChannel { }; struct NGuid { - uint32_t data1; - uint16_t data2; - uint16_t data3; - unsigned char data4[8]; + uint32_t data1; + uint16_t data2; + uint16_t data3; + unsigned char data4[8]; +}; + +struct BackEndpointCallbackInfo { + NGuid* caller = new NGuid(); + bool muted; + float mainVolume; + size_t channels; + float* channelVolumes = nullptr; }; class EndpointHandler { @@ -69,15 +77,20 @@ public: uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); NGuid* getGuid(); - void updateMuteCallback(uint64_t idx, bool muted); void setFrontMuteCallback(std::function f); void setFrontVolumeCallback(std::function f); - //void updateMainVolumeCallback(uint64_t idx, float newVal); - void updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal); + void setUpdateFrontCallback(std::function f); + //TODO: A EPH + BackEndpointCallbackInfo** callbackInfo = nullptr; + int callbackInfoSize = 0; + /* void updateMuteCallback(uint64_t idx, bool muted); */ + /* void updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal); */ + //void receiveBackEndpointCallback(uint64_t index); private: static Overseer os; std::vector endpointHandlers; + std::function updateFrontVolumeCallback; std::function updateFrontMuteCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 155cd90..3242b0d 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -16,7 +16,8 @@ * } */ -EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget(parent){ +EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + this->idx = idx; this->eph = eph; layout = new QGridLayout(); this->setLayout(layout); @@ -34,9 +35,13 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( mainSlider->setSingleStep(1); mainSlider->setRange(0,100); + //TODO: APARTE + osh->callbackInfo[idx]->muted = eph->getMute(); muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; + //TODO: APARTE + osh->callbackInfo[idx]->mainVolume = eph->getVolume(ENDPOINT_MASTER_VOLUME); mainSlider->setValue((int)volume); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); @@ -46,15 +51,24 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( mainMuteLayout->addWidget(muteButton, 0, 1); layout->addWidget(mainSlider, 0, 1); - connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); - connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); + /* + * connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); + */ + /* connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); + */ + //TODO: APARTE + osh->callbackInfo[idx]->channels = eph->getChannelCount(); + osh->callbackInfo[idx]->channelVolumes = (float*)calloc(osh->callbackInfo[idx]->channels, sizeof(float)); + for(uint32_t i = 0; i < eph->getChannelCount(); i++){ QSlider* tmp = new QSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); tmp->setTickInterval(5); tmp->setSingleStep(1); tmp->setRange(0,100); + //TODO: Aparte + osh->callbackInfo[idx]->channelVolumes[i] = eph->getVolume(i); volume = eph->getVolume(i) * 100; tmp->setValue((int) volume); tmpLb->setText(QString::number(volume)); @@ -63,8 +77,22 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( layout->addWidget(tmp, 1, i); layout->addWidget(tmpLb, 2, i); //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func - connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); + /* + * connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); + */ } + + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, [this, idx](){ + mainSlider->setValue((int)(osh->callbackInfo[idx]->mainVolume * 100)); + muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); + for(uint32_t i = 0; i < osh->callbackInfo[idx]->channels; i++){ + this->channelSliders.at(i)->setValue((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100)); + this->channelLabels.at(i)->setText(QString::number((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100))); + } + }); + timer->start(10); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); @@ -72,15 +100,16 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent) : QWidget( } void EndpointWidget::updateMute(bool muted){ - log_debugcpp("cliqui callboqui cloqui"); + //TODO: mutex test + //log_debugcpp("cliqui callboqui cloqui"); //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. //this->blockSignals(true); - //this->muteButton->blockSignals(true); + this->muteButton->blockSignals(true); //this->eph->setMute(osh->getGuid(), muted); this->muteButton->setChecked(muted); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - //this->muteButton->blockSignals(false); + this->muteButton->blockSignals(false); //this->blockSignals(false); } @@ -94,7 +123,8 @@ void EndpointWidget::updateMute(int checked){ } void EndpointWidget::updateMainVolume(int newValue){ - log_debugcpp("updateMainVolume slot."); + //TODO: mutex test + //log_debugcpp("updateMainVolume slot."); this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); } @@ -102,16 +132,19 @@ void EndpointWidget::updateVolume(uint32_t channel, float newValue){ //this->blockSignals(true); int newVal = newValue * 100; if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { - log_debugcpp("mainvolcallback int: " << newVal); + //TODO: mutex test + //log_debugcpp("mainvolcallback int: " << newVal); //this->mainSlider->blockSignals(true); if(this->mainSlider->value() != newVal) { + this->mainSlider->blockSignals(true); this->mainSlider->setValue(newVal); + this->mainSlider->blockSignals(false); } return; } - - log_debugcpp("Cont->Front Channel:: " << channel << " volcallback int: " << newVal); + //TODO: mutex test + //log_debugcpp("Cont->Front Channel:: " << channel << " volcallback int: " << newVal); for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { @@ -162,23 +195,35 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { } void MainWindow::reloadEndpointWidgets() { - unsigned int i = 0; + size_t i = 0; for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); - EndpointWidget *epw = new EndpointWidget(osh->getEndpointHandlers().at(i), widget); + //TODO: APARTE + if(i >= osh->callbackInfoSize) { + BackEndpointCallbackInfo** temp = (BackEndpointCallbackInfo**)calloc((i + 1), sizeof(BackEndpointCallbackInfo)); + memcpy(temp,osh->callbackInfo, i * sizeof(sizeof(BackEndpointCallbackInfo))); + free(osh->callbackInfo); + osh->callbackInfo = temp; + osh->callbackInfo[i] = new BackEndpointCallbackInfo(); + osh->callbackInfoSize++; + } + memcpy(osh->callbackInfo[i]->caller, osh->getGuid(),sizeof(NGuid) ); + + EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); ews.push_back(epw); layout->addWidget(epw, i, 0); + } layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { - if (device < ews.size()) - ews[device]->updateVolume(channel, value); + if (device < ews.size()) + ews.at(device)->updateVolume(channel, value); }); osh->setFrontMuteCallback([this](uint64_t device, bool muted) { if (device < ews.size()) - ews[device]->updateMute(muted); + ews.at(device)->updateMute(muted); }); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index c5eee14..2054f0c 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -10,6 +10,7 @@ #include #include #include +#include /* * #else * class QSlider; @@ -53,7 +54,7 @@ class EndpointWidget : public QWidget { Q_OBJECT public: - EndpointWidget(EndpointHandler* eph, QWidget *parent = nullptr); + EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); //TODO: get(); EndpointHandler* eph; void setIndex(uint64_t idx); From 1797b39b304849b5be9f5a5fbf1819956f615fd3 Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 16 Aug 2023 23:18:36 +0200 Subject: [PATCH 28/57] full functionality restored, slight cleanup --- src/back/backlasses.cpp | 54 ++++---------------------------------- src/cont/contclasses.cpp | 55 +-------------------------------------- src/cont/contclasses.h | 11 +++----- src/qt/qtclasses.cpp | 56 +++++++++++++--------------------------- src/qt/qtclasses.h | 1 - 5 files changed, 27 insertions(+), 150 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 28b8822..cdcb73f 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -39,17 +39,7 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - /* - * float extraChannelVol[pNotify->nChannels]; - * for (UINT i = 0; i < pNotify->nChannels; i++){ - * AUDIO_VOLUME_NOTIFICATION_DATA eventData = *pNotify; - * extraChannelVol[i] = pNotify->afChannelVolumes[i]; - * } - */ - - // osh->callbackInfo[this->ep->getIndex()]->caller = (NGuid)pNotify->guidEventContext; memcpy(osh->callbackInfo[this->ep->getIndex()]->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - osh->callbackInfo[this->ep->getIndex()]->muted = pNotify->bMuted; osh->callbackInfo[this->ep->getIndex()]->mainVolume = pNotify->fMasterVolume; osh->callbackInfo[this->ep->getIndex()]->channels = pNotify->nChannels; @@ -58,34 +48,10 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { do { osh->callbackInfo[this->ep->getIndex()]->channelVolumes[i] = pNotify->afChannelVolumes[i]; } while(i++ < pNotify->nChannels); - - //osh->receiveBackEndpointCallback(this->ep->getIndex()); - - /* NGuid* guid = osh->getGuid(); - * //AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - * if (memcmp(guid, &eventData.guidEventContext, sizeof(*guid)) == 0) { - * //log_debugcpp("Onnanokotify says You Shall Not Update Thy Interface."); - * } else { - * //log_debugcpp("Onnanokotify says Stored: " << guid->data1); - * //log_debugcpp("Onnanokotify says Grace of God: " << eventData.guidEventContext.Data1); - * osh->updateMuteCallback(this->ep->getIndex(), eventData.bMuted); - * osh->updateVolumeCallback(this->ep->getIndex(), AudioChannel::CHANNEL_MAIN ,eventData.fMasterVolume); - * //log_debugcpp("Onnanokotify says Reported Channel Qty: " << eventData.nChannels); - * - * - * for(UINT i = 0; i < eventData.nChannels; i++) { - * osh->updateVolumeCallback(this->ep->getIndex(), (uint32_t)i, extraChannelVol[i]); - * } - * } - */ return S_OK; } -/* EndpointCallback::~EndpointCallback(){ - * PAUDIO_VOLUME_NOTIFICATION_DATA->Release(); - * } */ - Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; this->idx = idx; @@ -129,11 +95,8 @@ uint32_t Endpoint::getChannelCount(){ bool Endpoint::getMute(){ BOOL mut; - if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - //TODO: mutex test - //log_debugcpp("back BOOL is " << mut); + if(FAILED(endpointVolume->GetMute(&mut))) { /* TIP: Below */ } bool mute = (bool)mut; - //log_debugcpp("translate to bool " << mute); return mute; } @@ -153,25 +116,18 @@ bool Endpoint::getMute(){ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { - //TODO: mutex test + //TIP: There used to be log messages here. Now, it's a ghost town. GUID tempMsGuid = NGuidToGUID(guid); if (channel == AudioChannel::CHANNEL_MAIN) { - if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) { //log_debugcpp("MASTER VOLUME FAILED"); - }; + if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) {}; } else { - //log_debugcpp("Windows: Channel being updated: " << channel); - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { /* log_debugcpp("CHANNEL "<< channel <<" VOLUME FAILED"); */ }; + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { }; } } void Endpoint::setMute(NGuid* guid, bool muted) { - //BOOL mut; - //log_debugcpp("bool mute arrives as " << mut); - //if(FAILED(endpointVolume->GetMute(&mut))) { log_debugcpp("si"); } - //log_debugcpp("translate to BOOL as " << mut); - //TODO: use new funcs GUID tempMsGuid = NGuidToGUID(guid); - if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_debugcpp("MUTE FAILED"); }; + if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { /* TIP: Above */ }; } void Endpoint::setCallback(EndpointCallback *epc){ diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 20ca757..b528c58 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -29,15 +29,12 @@ uint64_t EndpointHandler::getIndex(){ * -1 for master volume */ void EndpointHandler::setVolume(NGuid* guid, int channel, int value){ - if (channel == ENDPOINT_MASTER_VOLUME) + if (channel == AudioChannel::CHANNEL_MAIN) ep->setVolume(guid, channel, (float)value / 100); else ep->setVolume(guid, channel, (float)value / 100); } void EndpointHandler::setMute(NGuid* guid, bool muted){ - //Qt momento, de ahi el param? - //TODO: mutex test - //log_debugcpp("kinda handling the muting tbh"); ep->setMute(guid, muted); } @@ -100,53 +97,3 @@ NGuid* OverseerHandler::getGuid() { void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; } - -void OverseerHandler::setFrontVolumeCallback(std::function f) { - this->updateFrontVolumeCallback = f; -} - -void OverseerHandler::setFrontMuteCallback(std::function f) { - this->updateFrontMuteCallback = f; -} - -/* - * void OverseerHandler::updateMuteCallback(uint64_t idx, bool muted){ - * updateFrontMuteCallback(idx, muted); - * } - */ - -/* - * void OverseerHandler::updateMainVolumeCallback(uint64_t idx, float newVal){ - * - * } - */ - -/* - * void OverseerHandler::updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal){ - * if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { - * //TODO: mutex test - * //log_debugcpp("mainvolcallback float: " << newVal); - * updateFrontVolumeCallback(idx, AudioChannel::CHANNEL_MAIN, newVal); - * return; - * } - * - * - * // convert channel to bitmask - * uint32_t i = 0; - * while (i < channel) - * i++; - * uint32_t mask = (1 << i); - * //TODO: Mutex test - * //log_debugcpp("Back->Cont Channel: " << mask << " volcallback float: " << newVal); - * - * updateFrontVolumeCallback(idx, mask, newVal); - * } - */ - -/* - * void OverseerHandler::receiveBackEndpointCallback(uint64_t index) { - * if (memcmp(os.getGuid(), callbackInfo.caller, sizeof(*guid)) != 0) - * updateFrontCallback(uint64_t index, &callbackInfo); - * - * } - */ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 2ed4578..b8df406 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -77,21 +77,16 @@ public: uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); NGuid* getGuid(); - void setFrontMuteCallback(std::function f); - void setFrontVolumeCallback(std::function f); - void setUpdateFrontCallback(std::function f); + //TODO: A EPH BackEndpointCallbackInfo** callbackInfo = nullptr; int callbackInfoSize = 0; - /* void updateMuteCallback(uint64_t idx, bool muted); */ - /* void updateVolumeCallback(uint64_t idx, uint32_t channel, float newVal); */ - //void receiveBackEndpointCallback(uint64_t index); private: static Overseer os; std::vector endpointHandlers; - std::function updateFrontVolumeCallback; - std::function updateFrontMuteCallback; + //std::function updateFrontVolumeCallback; + //std::function updateFrontMuteCallback; }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 3242b0d..6d58322 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -52,11 +52,10 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare layout->addWidget(mainSlider, 0, 1); - /* - * connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); - */ - /* connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); - */ + + connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); + connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); + //TODO: APARTE osh->callbackInfo[idx]->channels = eph->getChannelCount(); osh->callbackInfo[idx]->channelVolumes = (float*)calloc(osh->callbackInfo[idx]->channels, sizeof(float)); @@ -77,13 +76,13 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare layout->addWidget(tmp, 1, i); layout->addWidget(tmpLb, 2, i); //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func - /* - * connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); - */ + connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); + } QTimer *timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, idx](){ + if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; mainSlider->setValue((int)(osh->callbackInfo[idx]->mainVolume * 100)); muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); @@ -100,8 +99,6 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } void EndpointWidget::updateMute(bool muted){ - //TODO: mutex test - //log_debugcpp("cliqui callboqui cloqui"); //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. //this->blockSignals(true); this->muteButton->blockSignals(true); @@ -114,17 +111,12 @@ void EndpointWidget::updateMute(bool muted){ } void EndpointWidget::updateMute(int checked){ - log_debugcpp("cliqui slOtty cloqui"); bool muted = (checked == 2 ? true : false); - log_debugcpp("int: " << checked << " bool: " << muted); this->eph->setMute(osh->getGuid(), muted); - //this->muteButton->setCheckState(); this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); } void EndpointWidget::updateMainVolume(int newValue){ - //TODO: mutex test - //log_debugcpp("updateMainVolume slot."); this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); } @@ -132,8 +124,7 @@ void EndpointWidget::updateVolume(uint32_t channel, float newValue){ //this->blockSignals(true); int newVal = newValue * 100; if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { - //TODO: mutex test - //log_debugcpp("mainvolcallback int: " << newVal); + //TIP: Above //this->mainSlider->blockSignals(true); if(this->mainSlider->value() != newVal) { @@ -143,8 +134,6 @@ void EndpointWidget::updateVolume(uint32_t channel, float newValue){ } return; } - //TODO: mutex test - //log_debugcpp("Cont->Front Channel:: " << channel << " volcallback int: " << newVal); for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { @@ -160,17 +149,6 @@ void EndpointWidget::updateVolume(uint32_t channel, float newValue){ //this->blockSignals(false); } -/* - * void EndpointWidget::toggleFrontEvents(bool active){ - * this->muteButton->blockSignals(active); - * this->mainSlider->blockSignals(active); - * for(uint32_t i = 0; i < this->channelSliders.size(); i++){ - * this->channelSliders.at(i)->blockSignals(active); - * } - * } - */ - - void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; } @@ -217,14 +195,16 @@ void MainWindow::reloadEndpointWidgets() { layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); - osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { - if (device < ews.size()) - ews.at(device)->updateVolume(channel, value); - }); - osh->setFrontMuteCallback([this](uint64_t device, bool muted) { - if (device < ews.size()) - ews.at(device)->updateMute(muted); - }); + /* + * osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { + * if (device < ews.size()) + * ews.at(device)->updateVolume(channel, value); + * }); + * osh->setFrontMuteCallback([this](uint64_t device, bool muted) { + * if (device < ews.size()) + * ews.at(device)->updateMute(muted); + * }); + */ } /* diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 2054f0c..5dc9f1d 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -72,7 +72,6 @@ public: //void updateMainVolume(float newValue); void updateVolume(uint32_t channel, float newValue); void updateMute(bool muted); - //void toggleFrontEvents(bool active); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); From b15e5d6df73aa5647519b52ce9df4a32ecf46a51 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 17 Aug 2023 12:40:32 +0200 Subject: [PATCH 29/57] fixed back->front channel bar desync --- src/back/backlasses.cpp | 6 +++--- src/global.h | 2 +- src/qt/qtclasses.cpp | 25 +++++++++++++++++-------- src/qt/qtclasses.h | 1 + 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index cdcb73f..546c863 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -80,10 +80,10 @@ std::wstring Endpoint::getName(){ float Endpoint::getVolume(int channel){ float volume; - if (channel == ENDPOINT_MASTER_VOLUME) { - if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { log_debugcpp("si");} + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(endpointVolume->GetMasterVolumeLevelScalar(&volume))) { /* log_debugcpp("si") */;} } else { - if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { log_debugcpp("si");} + if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { /* log_debugcpp("si"); */} } return volume; } diff --git a/src/global.h b/src/global.h index c4cf2af..b17f1bd 100644 --- a/src/global.h +++ b/src/global.h @@ -7,7 +7,7 @@ #include "debug.h" -#define ENDPOINT_MASTER_VOLUME -1 +//#define ENDPOINT_MASTER_VOLUME -1 /* #define ENDPOINT_LEFT_CHANNEL_VOLUME 0 */ /* #define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 */ diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 6d58322..583d767 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -39,9 +39,9 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare osh->callbackInfo[idx]->muted = eph->getMute(); muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - float volume = eph->getVolume(ENDPOINT_MASTER_VOLUME) * 100; + float volume = eph->getVolume(AudioChannel::CHANNEL_MAIN) * 100; //TODO: APARTE - osh->callbackInfo[idx]->mainVolume = eph->getVolume(ENDPOINT_MASTER_VOLUME); + osh->callbackInfo[idx]->mainVolume = eph->getVolume(AudioChannel::CHANNEL_MAIN); mainSlider->setValue((int)volume); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); @@ -75,21 +75,30 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->channelLabels.push_back(tmpLb); layout->addWidget(tmp, 1, i); layout->addWidget(tmpLb, 2, i); - //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func - connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); + //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func + //this causes channel bar desync when back -> front. blocksignals below fix it. huh. + connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); } - QTimer *timer = new QTimer(this); + timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, idx](){ if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); mainSlider->setValue((int)(osh->callbackInfo[idx]->mainVolume * 100)); muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); for(uint32_t i = 0; i < osh->callbackInfo[idx]->channels; i++){ - this->channelSliders.at(i)->setValue((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100)); - this->channelLabels.at(i)->setText(QString::number((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100))); + this->channelSliders.at(i)->blockSignals(true); + this->channelSliders.at(i)->setValue((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100)); + this->channelLabels.at(i)->setText(QString::number((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100))); + this->channelSliders.at(i)->blockSignals(false); } + //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); + *osh->callbackInfo[idx]->caller = *osh->getGuid(); + mainSlider->blockSignals(false); + muteButton->blockSignals(false); }); timer->start(10); @@ -117,7 +126,7 @@ void EndpointWidget::updateMute(int checked){ } void EndpointWidget::updateMainVolume(int newValue){ - this->eph->setVolume(osh->getGuid(), ENDPOINT_MASTER_VOLUME, newValue); + this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } void EndpointWidget::updateVolume(uint32_t channel, float newValue){ diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 5dc9f1d..53434b5 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -81,6 +81,7 @@ public slots: void updateMute(int checked); private: + QTimer* timer = nullptr; uint64_t idx; //std::vector *ephs; //std::vector *sliders; From 1a9f141087856c606d0951d7f00d2b03cce78676 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 17 Aug 2023 13:06:26 +0200 Subject: [PATCH 30/57] code cleanup, ready to debug --- src/qt/qtclasses.cpp | 96 +++++++++++++++++++++++--------------------- src/qt/qtclasses.h | 4 +- 2 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 583d767..a6bf603 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -77,15 +77,17 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare layout->addWidget(tmpLb, 2, i); //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func //this causes channel bar desync when back -> front. blocksignals below fix it. huh. - connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ this->eph->setVolume(osh->getGuid(), i, newValue); this->channelLabels.at(i)->setText(QString::number(newValue)); }); - + connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ + this->eph->setVolume(osh->getGuid(), i, newValue); + this->channelLabels.at(i)->setText(QString::number(newValue)); + }); } timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, idx](){ if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; - mainSlider->blockSignals(true); - muteButton->blockSignals(true); + //mainSlider->blockSignals(true); + //muteButton->blockSignals(true); mainSlider->setValue((int)(osh->callbackInfo[idx]->mainVolume * 100)); muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); @@ -97,8 +99,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); *osh->callbackInfo[idx]->caller = *osh->getGuid(); - mainSlider->blockSignals(false); - muteButton->blockSignals(false); + //mainSlider->blockSignals(false); + //muteButton->blockSignals(false); }); timer->start(10); @@ -107,17 +109,19 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("ENDPOINT_WIDGETED"); } -void EndpointWidget::updateMute(bool muted){ - //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. - //this->blockSignals(true); - this->muteButton->blockSignals(true); - - //this->eph->setMute(osh->getGuid(), muted); - this->muteButton->setChecked(muted); - this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - this->muteButton->blockSignals(false); - //this->blockSignals(false); -} +/* + * void EndpointWidget::updateMute(bool muted){ + * //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. + * //this->blockSignals(true); + * this->muteButton->blockSignals(true); + * + * //this->eph->setMute(osh->getGuid(), muted); + * this->muteButton->setChecked(muted); + * this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); + * this->muteButton->blockSignals(false); + * //this->blockSignals(false); + * } + */ void EndpointWidget::updateMute(int checked){ bool muted = (checked == 2 ? true : false); @@ -129,34 +133,36 @@ void EndpointWidget::updateMainVolume(int newValue){ this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } -void EndpointWidget::updateVolume(uint32_t channel, float newValue){ - //this->blockSignals(true); - int newVal = newValue * 100; - if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { - //TIP: Above - //this->mainSlider->blockSignals(true); - - if(this->mainSlider->value() != newVal) { - this->mainSlider->blockSignals(true); - this->mainSlider->setValue(newVal); - this->mainSlider->blockSignals(false); - } - return; - } - - for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { - if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { - //this->channelSliders.at(i)->blockSignals(true); - - this->channelSliders.at(i)->setValue(newVal); - this->channelLabels.at(i)->setText(QString::number((int)(newValue * 100))); - - //this->channelSliders.at(i)->blockSignals(false); - } - } - - //this->blockSignals(false); -} +/* + * void EndpointWidget::updateVolume(uint32_t channel, float newValue){ + * //this->blockSignals(true); + * int newVal = newValue * 100; + * if (channel == (uint32_t)AudioChannel::CHANNEL_MAIN) { + * //TIP: Above + * //this->mainSlider->blockSignals(true); + * + * if(this->mainSlider->value() != newVal) { + * this->mainSlider->blockSignals(true); + * this->mainSlider->setValue(newVal); + * this->mainSlider->blockSignals(false); + * } + * return; + * } + * + * for (size_t i = 0; i < sizeof(uint32_t) * 8 && i < channelSliders.size(); ++i) { + * if (((channel >> i) & 1) && this->channelSliders.at(i)->value() != newVal) { + * //this->channelSliders.at(i)->blockSignals(true); + * + * this->channelSliders.at(i)->setValue(newVal); + * this->channelLabels.at(i)->setText(QString::number((int)(newValue * 100))); + * + * //this->channelSliders.at(i)->blockSignals(false); + * } + * } + * + * //this->blockSignals(false); + * } + */ void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 53434b5..3011021 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -70,8 +70,8 @@ public: QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; //void updateMainVolume(float newValue); - void updateVolume(uint32_t channel, float newValue); - void updateMute(bool muted); + //void updateVolume(uint32_t channel, float newValue); + //void updateMute(bool muted); //void populateEndpointWidget(EndpointHandler *eph); //void setEndpointHandlers(std::vector *ephs); From 8e9de6a771506e41dfc209286cc1c175e4281473 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 17 Aug 2023 19:50:01 +0200 Subject: [PATCH 31/57] rounding factor and sndvol trick note: ready to move on --- src/qt/qtclasses.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index a6bf603..ace2e2b 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -50,9 +50,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainMuteLayout->addWidget(mainLabel, 0, 0); mainMuteLayout->addWidget(muteButton, 0, 1); layout->addWidget(mainSlider, 0, 1); - - - + + //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); @@ -82,25 +81,27 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->channelLabels.at(i)->setText(QString::number(newValue)); }); } - + + //Polling time timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, idx](){ - if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; - //mainSlider->blockSignals(true); - //muteButton->blockSignals(true); - mainSlider->setValue((int)(osh->callbackInfo[idx]->mainVolume * 100)); + //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; + const float roundingFactor = 0.005; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((osh->callbackInfo[idx]->mainVolume + roundingFactor) * 100)); muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); for(uint32_t i = 0; i < osh->callbackInfo[idx]->channels; i++){ this->channelSliders.at(i)->blockSignals(true); - this->channelSliders.at(i)->setValue((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100)); - this->channelLabels.at(i)->setText(QString::number((int)(osh->callbackInfo[idx]->channelVolumes[i] * 100))); + this->channelSliders.at(i)->setValue((int)((osh->callbackInfo[idx]->channelVolumes[i] + roundingFactor) * 100)); + this->channelLabels.at(i)->setText(QString::number((int)((osh->callbackInfo[idx]->channelVolumes[i] + roundingFactor) * 100))); this->channelSliders.at(i)->blockSignals(false); } //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); *osh->callbackInfo[idx]->caller = *osh->getGuid(); - //mainSlider->blockSignals(false); - //muteButton->blockSignals(false); + mainSlider->blockSignals(false); + muteButton->blockSignals(false); }); timer->start(10); @@ -130,6 +131,7 @@ void EndpointWidget::updateMute(int checked){ } void EndpointWidget::updateMainVolume(int newValue){ + //QObject* obj = sender(); this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } @@ -203,6 +205,7 @@ void MainWindow::reloadEndpointWidgets() { memcpy(osh->callbackInfo[i]->caller, osh->getGuid(),sizeof(NGuid) ); EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); + //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET ews.push_back(epw); layout->addWidget(epw, i, 0); From 8c1cea2da9c80547a2bd90b1ba58a6bc622baef1 Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 22 Aug 2023 18:28:09 +0200 Subject: [PATCH 32/57] polling data source vector'd and moved to eph --- src/back/backlasses.cpp | 18 ++++++++++------- src/cont/contclasses.cpp | 11 ++++++++++ src/cont/contclasses.h | 22 +++++--------------- src/qt/qtclasses.cpp | 43 +++++++++++++--------------------------- 4 files changed, 41 insertions(+), 53 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 546c863..6c16ceb 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -38,17 +38,21 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - - memcpy(osh->callbackInfo[this->ep->getIndex()]->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - osh->callbackInfo[this->ep->getIndex()]->muted = pNotify->bMuted; - osh->callbackInfo[this->ep->getIndex()]->mainVolume = pNotify->fMasterVolume; - osh->callbackInfo[this->ep->getIndex()]->channels = pNotify->nChannels; + + //TODO: MEMORY LEAK. FREE DATA4 FROM NGUID. + //TODO: el default = objcopy frees? + //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; + memcpy(osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); + + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; UINT i = 0; do { - osh->callbackInfo[this->ep->getIndex()]->channelVolumes[i] = pNotify->afChannelVolumes[i]; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[i] = pNotify->afChannelVolumes[i]; } while(i++ < pNotify->nChannels); - + return S_OK; } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index b528c58..f51125d 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -11,6 +11,17 @@ EndpointHandler::EndpointHandler(uint64_t idx) { epc = new EndpointCallback(ep); //epName = ep->getName(); ep->setCallback(epc); + callbackInfo.muted = this->getMute(); + callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); + callbackInfo.channels = this->getChannelCount(); + callbackInfo.channelVolumes.resize(this->callbackInfo.channels); + for(uint32_t i = 0; i < this->getChannelCount(); i++){ + callbackInfo.channelVolumes[i] = this->getVolume(i); + } +} + +BackEndpointCallbackInfo* EndpointHandler::getCallbackInfo(){ + return &this->callbackInfo; } uint32_t EndpointHandler::getChannelCount(){ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index b8df406..acfa66b 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,12 +1,6 @@ #pragma once -#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) -#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) - -/* #ifndef QTBLESSED */ -/* //#define Q_OBJECT */ -/* class QWidget{}; */ -/* class QMainWindow{}; */ -/* #endif */ +//#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) +//#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) class EndpointWidget; class Endpoint; @@ -31,7 +25,7 @@ struct BackEndpointCallbackInfo { bool muted; float mainVolume; size_t channels; - float* channelVolumes = nullptr; + std::vector channelVolumes; }; class EndpointHandler { @@ -43,6 +37,7 @@ public: EndpointCallback *epc = nullptr; //std::wstring epName; + BackEndpointCallbackInfo* getCallbackInfo(); uint32_t getChannelCount(); void setIndex(uint64_t idx); @@ -59,11 +54,8 @@ public: ~EndpointHandler(); private: uint64_t idx; + BackEndpointCallbackInfo callbackInfo; //QSlider *slidy; - -//signals: - - }; @@ -77,10 +69,6 @@ public: uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); NGuid* getGuid(); - - //TODO: A EPH - BackEndpointCallbackInfo** callbackInfo = nullptr; - int callbackInfoSize = 0; private: static Overseer os; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index ace2e2b..4da3f95 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -35,13 +35,9 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider->setSingleStep(1); mainSlider->setRange(0,100); - //TODO: APARTE - osh->callbackInfo[idx]->muted = eph->getMute(); muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(AudioChannel::CHANNEL_MAIN) * 100; - //TODO: APARTE - osh->callbackInfo[idx]->mainVolume = eph->getVolume(AudioChannel::CHANNEL_MAIN); mainSlider->setValue((int)volume); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); @@ -55,9 +51,6 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); - //TODO: APARTE - osh->callbackInfo[idx]->channels = eph->getChannelCount(); - osh->callbackInfo[idx]->channelVolumes = (float*)calloc(osh->callbackInfo[idx]->channels, sizeof(float)); for(uint32_t i = 0; i < eph->getChannelCount(); i++){ QSlider* tmp = new QSlider(Qt::Horizontal); @@ -65,8 +58,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare tmp->setTickInterval(5); tmp->setSingleStep(1); tmp->setRange(0,100); - //TODO: Aparte - osh->callbackInfo[idx]->channelVolumes[i] = eph->getVolume(i); + volume = eph->getVolume(i) * 100; tmp->setValue((int) volume); tmpLb->setText(QString::number(volume)); @@ -84,22 +76,25 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //Polling time timer = new QTimer(this); - connect(timer, &QTimer::timeout, [this, idx](){ - //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; + connect(timer, &QTimer::timeout, [this, eph](){ + //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. const float roundingFactor = 0.005; mainSlider->blockSignals(true); muteButton->blockSignals(true); - mainSlider->setValue((int)((osh->callbackInfo[idx]->mainVolume + roundingFactor) * 100)); - muteButton->setCheckState((osh->callbackInfo[idx]->muted == false ? Qt::Unchecked : Qt::Checked)); - muteButton->setText(osh->callbackInfo[idx]->muted ? STRING_UNMUTE : STRING_MUTE); - for(uint32_t i = 0; i < osh->callbackInfo[idx]->channels; i++){ + mainSlider->setValue((int)((eph->getCallbackInfo()->mainVolume + roundingFactor) * 100)); + muteButton->setCheckState((eph->getCallbackInfo()->muted == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(eph->getCallbackInfo()->muted ? STRING_UNMUTE : STRING_MUTE); + for(uint32_t i = 0; i < eph->getCallbackInfo()->channels; i++){ this->channelSliders.at(i)->blockSignals(true); - this->channelSliders.at(i)->setValue((int)((osh->callbackInfo[idx]->channelVolumes[i] + roundingFactor) * 100)); - this->channelLabels.at(i)->setText(QString::number((int)((osh->callbackInfo[idx]->channelVolumes[i] + roundingFactor) * 100))); + this->channelSliders.at(i)->setValue((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100)); + this->channelLabels.at(i)->setText(QString::number((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100))); this->channelSliders.at(i)->blockSignals(false); } //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); - *osh->callbackInfo[idx]->caller = *osh->getGuid(); + + //TODO: el default = objcopy frees? + //delete eph->getCallbackInfo()->caller; + *eph->getCallbackInfo()->caller = *osh->getGuid(); mainSlider->blockSignals(false); muteButton->blockSignals(false); }); @@ -193,17 +188,7 @@ void MainWindow::reloadEndpointWidgets() { size_t i = 0; for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); - //TODO: APARTE - if(i >= osh->callbackInfoSize) { - BackEndpointCallbackInfo** temp = (BackEndpointCallbackInfo**)calloc((i + 1), sizeof(BackEndpointCallbackInfo)); - memcpy(temp,osh->callbackInfo, i * sizeof(sizeof(BackEndpointCallbackInfo))); - free(osh->callbackInfo); - osh->callbackInfo = temp; - osh->callbackInfo[i] = new BackEndpointCallbackInfo(); - osh->callbackInfoSize++; - } - memcpy(osh->callbackInfo[i]->caller, osh->getGuid(),sizeof(NGuid) ); - + *osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = *osh->getGuid(); EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET ews.push_back(epw); From e1dd3dc532d833eb8b467dee33700021b6408a41 Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 23 Aug 2023 23:12:30 +0200 Subject: [PATCH 33/57] stoopid pointer --- src/back/backlasses.cpp | 2 +- src/cont/contclasses.h | 2 +- src/qt/qtclasses.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 6c16ceb..488ba86 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -42,7 +42,7 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { //TODO: MEMORY LEAK. FREE DATA4 FROM NGUID. //TODO: el default = objcopy frees? //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; - memcpy(osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); + memcpy(&osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index acfa66b..2e3e053 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -21,7 +21,7 @@ struct NGuid { }; struct BackEndpointCallbackInfo { - NGuid* caller = new NGuid(); + NGuid caller; bool muted; float mainVolume; size_t channels; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 4da3f95..74900de 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -94,7 +94,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //TODO: el default = objcopy frees? //delete eph->getCallbackInfo()->caller; - *eph->getCallbackInfo()->caller = *osh->getGuid(); + eph->getCallbackInfo()->caller = *osh->getGuid(); mainSlider->blockSignals(false); muteButton->blockSignals(false); }); @@ -188,7 +188,7 @@ void MainWindow::reloadEndpointWidgets() { size_t i = 0; for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); - *osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = *osh->getGuid(); + osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = *osh->getGuid(); EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET ews.push_back(epw); From 1fcf6c372265543448db17716ed2da7985dbe316 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 24 Aug 2023 17:08:22 +0200 Subject: [PATCH 34/57] fixed memory leak --- src/back/backlasses.cpp | 16 ++++++++++++++-- src/cont/contclasses.h | 9 +++++++++ src/qt/qtclasses.cpp | 1 - 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 488ba86..dba9c69 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -41,8 +41,20 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { //TODO: MEMORY LEAK. FREE DATA4 FROM NGUID. //TODO: el default = objcopy frees? - //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; - memcpy(&osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); + //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; + //osh->getEndpointHandlers().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->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ + = pNotify->guidEventContext.Data1; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ + = pNotify->guidEventContext.Data2; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ + = pNotify->guidEventContext.Data3; + for(int i = 0; i < 8 /* Data4 size */; i++){ + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = pNotify->guidEventContext.Data4[i]; + } + + //memcpy(&osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 2e3e053..3cde72a 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -18,6 +18,15 @@ struct NGuid { uint16_t data2; uint16_t data3; unsigned char data4[8]; + + + /* void freeData4(){ */ + /* int i = 0; */ + /* do{ */ + /* if(this->data4 + i != nullptr) free(data4 + i); */ + /* i++; */ + /* }while (i < 8); */ + /* } */ }; struct BackEndpointCallbackInfo { diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 74900de..bd3c220 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -93,7 +93,6 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); //TODO: el default = objcopy frees? - //delete eph->getCallbackInfo()->caller; eph->getCallbackInfo()->caller = *osh->getGuid(); mainSlider->blockSignals(false); muteButton->blockSignals(false); From 5a9d2f7099a40027f29d485b2648db0b85d83bb5 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 24 Aug 2023 19:40:19 +0200 Subject: [PATCH 35/57] implemented tray icon --- assets.qrc | 5 +++ assets/notificationAreaIcon.png | Bin 0 -> 159931 bytes qtest.pro | 6 ++- src/global.h | 7 ++- src/qt/qtclasses.cpp | 75 +++++++++++++++++--------------- src/qt/qtclasses.h | 18 +++++++- src/qtestmain.cpp | 1 + 7 files changed, 70 insertions(+), 42 deletions(-) create mode 100644 assets.qrc create mode 100644 assets/notificationAreaIcon.png diff --git a/assets.qrc b/assets.qrc new file mode 100644 index 0000000..8b63932 --- /dev/null +++ b/assets.qrc @@ -0,0 +1,5 @@ + + + assets/notificationAreaIcon.png + + diff --git a/assets/notificationAreaIcon.png b/assets/notificationAreaIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..025233261a0746d45ee9bcedf8d9a430b3e75377 GIT binary patch literal 159931 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

yntZZuqQY-Ovg+gg;24agy2{;)OhFL=XJ|nKE8S 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

Zm;9Ug^d?mU)=Kv;`9Ax>M>Ts<>gQp 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 O+1!Nxm-73=7yk#+hpz(w literal 0 HcmV?d00001 diff --git a/qtest.pro b/qtest.pro index 511e78a..c96012e 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,12 +1,16 @@ QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview #QMAKE_LINK += clang++ -QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview -Wl,-pdb= -v +QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v + DEFINES += DEBUG CONFIG += debug console + QT += widgets INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h +RESOURCES = assets.qrc + #DESTDIR += "build" diff --git a/src/global.h b/src/global.h index b17f1bd..8d5a768 100644 --- a/src/global.h +++ b/src/global.h @@ -7,12 +7,11 @@ #include "debug.h" -//#define ENDPOINT_MASTER_VOLUME -1 -/* #define ENDPOINT_LEFT_CHANNEL_VOLUME 0 */ -/* #define ENDPOINT_RIGHT_CHANNEL_VOLUME 1 */ - +//TODO: Use tr();? QTranslator #define STRING_MUTE "Mute" #define STRING_UNMUTE "Unmute" +#define STRING_QUIT "Quit" +#define STRING_TITLE "Mixer Fachero" //INIT BACK class OverseerHandler; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index bd3c220..53bd91a 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,21 +1,5 @@ #include "qtclasses.h" -/* - * ToggleButton::ToggleButton(QWidget *parent) : QAbstractButton(parent) { - * this->setCheckable(true); - * } - * - * ToggleButton::~ToggleButton(){ - * - * } - * - * void ToggleButton::checkStateSet(){ } - * - * bool hitButton(const QPoint &pos) { - * - * } - */ - EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ this->idx = idx; this->eph = eph; @@ -173,14 +157,53 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // setCentralWidget(centralWidget); widget = new QWidget(); layout = new QGridLayout(); + trayIcon = new QSystemTrayIcon(); + trayIconMenu = new QMenu(); + trayIconMenuQuit = new QAction(STRING_QUIT); widget->setLayout(layout); setCentralWidget(widget); //layout->addWidget(pintas, 0, 0); - setWindowTitle("slidea resbala nu c"); + setWindowTitle(STRING_TITLE); reloadEndpointWidgets(); + + //Tray Icon + trayIconMenu->addSeparator(); + trayIconMenu->addAction(trayIconMenuQuit); + connect(trayIconMenuQuit, &QAction::triggered, qApp, &QCoreApplication::quit); + trayIcon->setIcon(QIcon(":/assets/notificationAreaIcon.png")); + setWindowIcon(QIcon(":/assets/notificationAreaIcon.png")); + //TODO: Extend qsystemtrayicon to change mouse click? + //show before setting tooltip required; smells like bug to me! + trayIcon->show(); + trayIcon->setToolTip(STRING_TITLE); + trayIcon->setContextMenu(trayIconMenu); + QString as = trayIcon->toolTip(); + connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if (!event->spontaneous() || !isVisible()) return; + + if (trayIcon->isVisible()) { + //todo: would be nice to show this to 1st time users; ini-san will come... + //this->trayIcon->showMessage("ini file calling","tratarte como un gilipollas la primera vez", QSystemTrayIcon::Information); + + hide(); + event->ignore(); + } +} + +void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { + switch (reason) { + case QSystemTrayIcon::Trigger: + this->showNormal(); + break; + default: + break; + } } void MainWindow::reloadEndpointWidgets() { @@ -209,21 +232,3 @@ void MainWindow::reloadEndpointWidgets() { */ } -/* - * void MainWindow::setPlotButton() { - * button = new QPushButton("push"), - * button->setCheckable(true); - * connect(button, SIGNAL(toggled(bool)), this, SLOT(toggled(bool))) - * QHBoxLayout *plotsLayout = new QHBoxLayout; - * plotsLayout->setSpacing(10); - * plotsLayout->addWidget(funPlot); - * QHBoxLayout *buttonsLayout = new QHBoxLayout ; - * buttonsLayout->addWidget(button); - * QVBoxLayout *widgetLayout = new QVBoxLayout; - * widgetLayout->addLayout(plotsLayout); - * widgetLayout->addLayout(buttonsLayout); - * setLayout(widgetLayout); - * ... - */ - - diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 3011021..b8cffe8 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -5,6 +5,13 @@ #include #include +#include + +#include +#include +#include +//#include + #include #include #include @@ -79,7 +86,7 @@ public: public slots: void updateMainVolume(int newValue); void updateMute(int checked); - + private: QTimer* timer = nullptr; uint64_t idx; @@ -99,6 +106,11 @@ public: MainWindow(QWidget *parent = nullptr); void reloadEndpointWidgets(); +protected: + void closeEvent(QCloseEvent *event) override; + +private slots: + void trayIconActivated(QSystemTrayIcon::ActivationReason reason); //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); @@ -107,8 +119,10 @@ private: std::vector ews; QWidget *widget; QGridLayout *layout; - //QLabel *pintas; + QSystemTrayIcon *trayIcon; + QMenu *trayIconMenu; + QAction *trayIconMenuQuit; //public slots: // void setEndpointHandlers(std::vector *ephs); diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 15d18fc..22d5e9f 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -30,6 +30,7 @@ int main (int argc, char* argv[]) { QScopedPointer app(createApplication(argc, argv)); MainWindow window = MainWindow(); //window.setEndpointHandlers(ephs); + QApplication::setQuitOnLastWindowClosed(false); app->setStyle("windowsvista"); window.show(); return app->exec(); From 5c8c1509c879d049173ee11811f76f418606b450 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 24 Aug 2023 20:23:24 +0200 Subject: [PATCH 36/57] poc prevent duplicate instances --- qtest.pro | 2 +- src/qtestmain.cpp | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/qtest.pro b/qtest.pro index c96012e..8e3d0d0 100644 --- a/qtest.pro +++ b/qtest.pro @@ -5,7 +5,7 @@ QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v DEFINES += DEBUG CONFIG += debug console -QT += widgets +QT += widgets network INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 22d5e9f..3b37996 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -5,8 +5,11 @@ //#define QTBLESSED #include -#include +#include +#include +#include +#include //#include "contclasses.h" #include "qtclasses.h" #include "global.h" @@ -18,9 +21,29 @@ QApplication* createApplication(int &argc, char *argv[]) return new QApplication(argc, argv); } +bool isSingleInstanceRunning(QString appName) { + QLocalSocket socket; + socket.connectToServer(appName); + bool isOpen = socket.isOpen(); + socket.close(); + return isOpen; +} + +QLocalServer* startSingleInstanceServer(QString appName) { + QLocalServer* server = new QLocalServer; + server->setSocketOptions(QLocalServer::WorldAccessOption); + server->listen(appName); + return server; +} int main (int argc, char* argv[]) { //QApplication::setStyle("windowsvista"); + //Check if running + //https://stackoverflow.com/questions/48060989/qt-show-application-if-currently-running + if (!isSingleInstanceRunning("Mixer")) + startSingleInstanceServer("Mixer"); + else exit(0); + //INIT CONT log_debugcpp("main init"); osh->reloadEndpointHandlers(); From e278280c4bd105315fd2d87f0b37ca0d57749355 Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 29 Aug 2023 19:00:07 +0200 Subject: [PATCH 37/57] phantom header and default role recollection --- qtest.pro | 2 +- src/back/backlasses.cpp | 107 +++++++++++++++++------ src/back/backlasses.h | 21 +++-- src/back/ipolicyconfig.h | 180 +++++++++++++++++++++++++++++++++++++++ src/cont/contclasses.cpp | 20 ++++- src/cont/contclasses.h | 35 ++++++-- src/debug.h | 5 ++ src/global.h | 7 +- src/qt/qtclasses.cpp | 20 +++++ src/qt/qtclasses.h | 3 + 10 files changed, 354 insertions(+), 46 deletions(-) create mode 100644 src/back/ipolicyconfig.h diff --git a/qtest.pro b/qtest.pro index 8e3d0d0..b4af8be 100644 --- a/qtest.pro +++ b/qtest.pro @@ -10,7 +10,7 @@ INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp -HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h +HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h ipolicyconfig.h RESOURCES = assets.qrc #DESTDIR += "build" diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index dba9c69..40abc4c 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,15 +1,15 @@ #include #include -EndpointCallback::EndpointCallback(Endpoint* ep){ +EndpointVolumeCallback::EndpointVolumeCallback(Endpoint* ep){ this->ep = ep; } -ULONG EndpointCallback::AddRef(){ +ULONG EndpointVolumeCallback::AddRef(){ return InterlockedIncrement(&ref); } -ULONG EndpointCallback::Release(){ +ULONG EndpointVolumeCallback::Release(){ ULONG tempRef = InterlockedDecrement(&ref); if (tempRef == 0) { delete this; @@ -17,7 +17,7 @@ ULONG EndpointCallback::Release(){ return tempRef; } -HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { +HRESULT EndpointVolumeCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { if (IID_IUnknown == riid) { AddRef(); @@ -36,10 +36,9 @@ HRESULT EndpointCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { return S_OK; } -HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { +HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - //TODO: MEMORY LEAK. FREE DATA4 FROM NGUID. //TODO: el default = objcopy frees? //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; //osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.freeData4(); @@ -71,10 +70,24 @@ HRESULT EndpointCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; this->idx = idx; - if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { log_debugcpp("si"); }; - if (FAILED(endpointVolume->GetChannelCount(&channelCount))) log_debugcpp("get channel count fail"); - //Obtaining friendly name: IPropertyStore creates PROPVARIANT per field - // hr = endpointPtr->GetId(&endpointID); + //if(FAILED()) {}; + DWORD tempState = 0; + if(FAILED(endpoint->GetState(&tempState))) {exit(-1);}; + this->endpointState = tempState; + + if (tempState == DEVICE_STATE_ACTIVE) { + if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { /* log_debugcpp("si"); */ }; + + if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {};/* log_debugcpp("get channel count fail"); */ + } else channelCount = 0; + + //todo:: atexit into exit Gather ID + LPWSTR tempString = nullptr; + if (FAILED(endpoint->GetId(&tempString))) {exit(-1);}; + endpointId = std::wstring(tempString); + log_wdebugcpp(endpointId); + CoTaskMemFree(tempString); + endpoint->OpenPropertyStore(STGM_READ, &properties); PROPVARIANT pv; properties->GetValue(PKEY_Device_FriendlyName , &pv); @@ -94,6 +107,10 @@ std::wstring Endpoint::getName(){ return friendlyName; } +std::wstring Endpoint::getId(){ + return endpointId; +} + float Endpoint::getVolume(int channel){ float volume; if (channel == AudioChannel::CHANNEL_MAIN) { @@ -116,19 +133,13 @@ bool Endpoint::getMute(){ return mute; } -/* - * float Endpoint::getLeftChannelVolume(){ - * float volume; - * if(FAILED(endpointVolume-> GetChannelVolumeLevelScalar(0, &volume)) { log_debugcpp("si"); } ); - * return volume; - * } - * - * float Endpoint::getRightChannelVolume(){ - * float volume; - * if(FAILED(endpointVolume-> GetChannelVolumeLevelScalar(1, &volume)) { log_debugcpp("si");} - * return volume; - * } - */ +void Endpoint::setState(uint8_t state){ + this->endpointState = state; +} + +uint8_t Endpoint::getState(){ + return this->endpointState; +} void Endpoint::setVolume(NGuid* guid, int channel, float volume) { @@ -146,16 +157,26 @@ void Endpoint::setMute(NGuid* guid, bool muted) { if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { /* TIP: Above */ }; } -void Endpoint::setCallback(EndpointCallback *epc){ +void Endpoint::setVolumeCallback(EndpointVolumeCallback *epc){ endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } -void Endpoint::removeCallback(EndpointCallback *epc){ +void Endpoint::removeVolumeCallback(EndpointVolumeCallback *epc){ endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } +uint8_t Endpoint::getRoles(){ + return this->endpointRoles; +} + +void Endpoint::setRoles(uint8_t role){ + //todo: operador virtuoso + uint8_t roles = endpointRoles | role; + this->endpointRoles = roles; +} + Endpoint::~Endpoint(){ - log_debugcpp("cum"); + log_debugcpp("murio endpoint-san uwu"); properties->Release(); endpointVolume->Release(); endpoint->Release(); @@ -183,7 +204,7 @@ void Overseer::initCOMLibrary() { void Overseer::reloadEndpoints() { IMMDeviceCollection *deviceCollection; // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) )) + if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE , &deviceCollection) )) { log_debugcpp("si"); }; @@ -193,8 +214,8 @@ void Overseer::reloadEndpoints() { //Retrieving actual endpoints and storing them on their own class + IMMDevice *temp; for (unsigned int i = 0; i < numPlaybackEndpoints; i++){ - IMMDevice *temp; if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; Endpoint *endpoint = new Endpoint(temp, i); //endpoint->setIndex(i); @@ -203,6 +224,36 @@ void Overseer::reloadEndpoints() { } deviceCollection->Release(); + + //Discerning default endpoints per role + //order: console, multimedia, communications + for(int i = 0; i < ERole_enum_count; i++){ + ERole val; + switch(i) { + case 0: + val = eConsole; + break; + case 1: + val = eMultimedia; + break; + case 2: + val = eCommunications; + break; + } + deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, val, &temp); + LPWSTR id = nullptr; + + for (unsigned int j = 0; j < numPlaybackEndpoints; j++){ + std::wstring test = playbackDevices.at(j)->getId(); + temp->GetId(&id); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, test.c_str(), -987, id, -987, NULL, NULL, 0); + if (comparison - 2 == 0) { + log_wdebugcpp("ola defaul de " << i << " es " << id); + playbackDevices.at(j)->setRoles((1 << i)); + } + //uint8_t debg = playbackDevices.at(j)->getRoles(); + } + } } Overseer::Overseer(){ diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 0e816a8..f79de72 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -19,11 +19,12 @@ //#include //#include #include +#include #include "global.h" #include "contclasses.h" -class EndpointCallback; +class EndpointVolumeCallback; class Endpoint { @@ -36,9 +37,14 @@ class Endpoint { float getVolume(int channel); void setMute(NGuid* guid, bool muted); bool getMute(); + void setState(uint8_t state); + uint8_t getState(); + uint8_t getRoles(); + void setRoles(uint8_t role); + std::wstring getId(); std::wstring getName(); - void setCallback(EndpointCallback *epc); - void removeCallback(EndpointCallback *epc); + void setVolumeCallback(EndpointVolumeCallback *epc); + void removeVolumeCallback(EndpointVolumeCallback *epc); ~Endpoint(); private: @@ -47,20 +53,23 @@ class Endpoint { IAudioEndpointVolume *endpointVolume ; IPropertyStore *properties; std::wstring friendlyName; + std::wstring endpointId; + uint8_t endpointState; + uint8_t endpointRoles = 0; uint64_t idx; // LPWSTR endpointID = NULL; }; -class EndpointCallback : public IAudioEndpointVolumeCallback { +class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { public: - EndpointCallback(Endpoint* ep); + EndpointVolumeCallback(Endpoint* ep); ULONG AddRef(); ULONG Release(); HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); HRESULT OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA update); - //~EndpointCallback(); + //~EndpointVolumeCallback(); private: ULONG ref = 1; diff --git a/src/back/ipolicyconfig.h b/src/back/ipolicyconfig.h new file mode 100644 index 0000000..203188c --- /dev/null +++ b/src/back/ipolicyconfig.h @@ -0,0 +1,180 @@ +// ---------------------------------------------------------------------------- +// PolicyConfig.h +// Undocumented COM-interface IPolicyConfig. +// Use for set default audio render endpoint +// @author EreTIk +// ---------------------------------------------------------------------------- + + +#pragma once + + +interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") +IPolicyConfig; +class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") +CPolicyConfigClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigClient +// {870af99c-171d-4f9e-af0d-e63df40c2bc9} +// +// interface IPolicyConfig +// {f8679f50-850a-41cf-9c72-430f290290c8} +// +// Query interface: +// CComPtr PolicyConfig; +// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient)); +// +// @compatible: Windows 7 and Later +// ---------------------------------------------------------------------------- +interface IPolicyConfig : public IUnknown +{ +public: + + virtual HRESULT GetMixFormat( + PCWSTR, + WAVEFORMATEX ** + ); + + virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( + PCWSTR, + INT, + WAVEFORMATEX ** + ); + + virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat( + PCWSTR + ); + + virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( + PCWSTR, + WAVEFORMATEX *, + WAVEFORMATEX * + ); + + virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod( + PCWSTR, + INT, + PINT64, + PINT64 + ); + + virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod( + PCWSTR, + PINT64 + ); + + virtual HRESULT STDMETHODCALLTYPE GetShareMode( + PCWSTR, + struct DeviceShareMode * + ); + + virtual HRESULT STDMETHODCALLTYPE SetShareMode( + PCWSTR, + struct DeviceShareMode * + ); + + virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT * + ); + + virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( + PCWSTR, + const PROPERTYKEY &, + PROPVARIANT * + ); + + virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( + __in PCWSTR wszDeviceId, + __in ERole eRole + ); + + virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( + PCWSTR, + INT + ); +}; + +interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") +IPolicyConfigVista; +class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") +CPolicyConfigVistaClient; +// ---------------------------------------------------------------------------- +// class CPolicyConfigVistaClient +// {294935CE-F637-4E7C-A41B-AB255460B862} +// +// interface IPolicyConfigVista +// {568b9108-44bf-40b4-9006-86afe5b5a620} +// +// Query interface: +// CComPtr 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/cont/contclasses.cpp b/src/cont/contclasses.cpp index f51125d..edfa18b 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -8,9 +8,9 @@ Overseer OverseerHandler::os; EndpointHandler::EndpointHandler(uint64_t idx) { //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); this->ep = osh->getPlaybackEndpoints().at(idx); - epc = new EndpointCallback(ep); + epc = new EndpointVolumeCallback(ep); //epName = ep->getName(); - ep->setCallback(epc); + ep->setVolumeCallback(epc); callbackInfo.muted = this->getMute(); callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); callbackInfo.channels = this->getChannelCount(); @@ -20,7 +20,7 @@ EndpointHandler::EndpointHandler(uint64_t idx) { } } -BackEndpointCallbackInfo* EndpointHandler::getCallbackInfo(){ +BackEndpointVolumeCallbackInfo* EndpointHandler::getCallbackInfo(){ return &this->callbackInfo; } @@ -61,8 +61,20 @@ bool EndpointHandler::getMute(){ return ep->getMute(); } +uint8_t EndpointHandler::getState(){ + return ep->getState(); +} + +void EndpointHandler::setState(uint8_t state){ + ep->setState(state); +} + +uint8_t EndpointHandler::getRoles(){ + return ep->getRoles(); +} + EndpointHandler::~EndpointHandler() { - ep->removeCallback(epc); + ep->removeVolumeCallback(epc); epc->Release(); delete ep; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 3cde72a..0b8b733 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -4,7 +4,7 @@ class EndpointWidget; class Endpoint; -class EndpointCallback; +class EndpointVolumeCallback; class Overseer; enum AudioChannel { @@ -13,6 +13,26 @@ enum AudioChannel { CHANNEL_MAIN = ~0, }; +enum EndpointState { + ENDPOINT_ACTIVE = (1 << 0), + ENDPOINT_DISABLED = (1 << 1), + ENDPOINT_NOTPRESENT = (1 << 2), + ENDPOINT_UNPLUGGED = (1 << 3), +}; + +enum Flows { + FLOW_PLAYBACK = (1 << 0), + FLOW_CAPTURE = (1 << 1), + FLOW_BOTH = (1 << 2), +}; + +enum Roles { + ROLE_CONSOLE = (1 << 0), + ROLE_MULTIMEDIA = (1 << 1), + ROLE_COMMUNICATIONS = (1 << 2), + ROLE_ALL = (7 << 0), +}; + struct NGuid { uint32_t data1; uint16_t data2; @@ -29,7 +49,7 @@ struct NGuid { /* } */ }; -struct BackEndpointCallbackInfo { +struct BackEndpointVolumeCallbackInfo { NGuid caller; bool muted; float mainVolume; @@ -43,10 +63,10 @@ public: EndpointHandler(uint64_t idx); //TODO: get(); Endpoint *ep = nullptr; - EndpointCallback *epc = nullptr; + EndpointVolumeCallback *epc = nullptr; //std::wstring epName; - BackEndpointCallbackInfo* getCallbackInfo(); + BackEndpointVolumeCallbackInfo* getCallbackInfo(); uint32_t getChannelCount(); void setIndex(uint64_t idx); @@ -56,14 +76,17 @@ public: std::wstring getName(); float getVolume(int channel); bool getMute(); - + uint8_t getState(); + uint8_t getRoles(); + void setVolume(NGuid* guid, int channel, int value); void setMute(NGuid* guid, bool muted); + void setState(uint8_t state); ~EndpointHandler(); private: uint64_t idx; - BackEndpointCallbackInfo callbackInfo; + BackEndpointVolumeCallbackInfo callbackInfo; //QSlider *slidy; }; diff --git a/src/debug.h b/src/debug.h index e80893f..8e309cf 100644 --- a/src/debug.h +++ b/src/debug.h @@ -12,10 +12,15 @@ std::bitset varToBitset(T info) { std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ } while (0) +#define log_wdebugcpp(str) do { \ + std::wcout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ + } while (0) + #define print_as_binary(len, type, info) varToBitset(info) #else #define log_debugcpp(str) +#define log_wdebugcpp(str) #define print_as_binary(len, type, info) #endif diff --git a/src/global.h b/src/global.h index 8d5a768..0ae2a75 100644 --- a/src/global.h +++ b/src/global.h @@ -11,7 +11,12 @@ #define STRING_MUTE "Mute" #define STRING_UNMUTE "Unmute" #define STRING_QUIT "Quit" -#define STRING_TITLE "Mixer Fachero" +#define STRING_TITLE "Mixer Fachero" + +#define STRING_ROLE_CONSOLE "Console" +#define STRING_ROLE_MULTIMEDIA "Multimedia" +#define STRING_ROLE_COMMUNICATIONS "Communications" +#define STRING_ROLE_ALL "All" //INIT BACK class OverseerHandler; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 53bd91a..9641e04 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -8,6 +8,14 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("olaW"); if (parent == nullptr) { log_debugcpp("owo?"); } + + defaultRolesCheckBoxes = { + {Roles::ROLE_ALL, new QCheckBox()}, + {Roles::ROLE_CONSOLE, new QCheckBox()}, + {Roles::ROLE_MULTIMEDIA, new QCheckBox()}, + {Roles::ROLE_COMMUNICATIONS, new QCheckBox()} + }; + muteButton = new QCheckBox(); mainLabel = new QLabel(QString::fromStdWString(eph->getName())); mainSlider = new QSlider(Qt::Horizontal); @@ -58,6 +66,18 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare }); } + uint8_t assignedRoles = eph->getRoles(); + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(assignedRoles == Roles::ROLE_ALL ? Qt::Checked : Qt::Unchecked); + /* + * {ROLE_ALL, new QCheckBox(this)}, + * {ROLE_CONSOLE, new QCheckBox(this)}, + * {ROLE_MULTIMEDIA, new QCheckBox(this)}, + * {ROLE_COMMUNICATIONS, new QCheckBox(this)} + * }; + */ + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 3, 0); + + //Polling time timer = new QTimer(this); connect(timer, &QTimer::timeout, [this, eph](){ diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index b8cffe8..4082fc7 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -76,6 +76,8 @@ public: std::vector channelLabels; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; + std::map defaultRolesCheckBoxes; + //void updateMainVolume(float newValue); //void updateVolume(uint32_t channel, float newValue); //void updateMute(bool muted); @@ -88,6 +90,7 @@ public slots: void updateMute(int checked); private: + size_t defaultRolesVectorSize = 4; QTimer* timer = nullptr; uint64_t idx; //std::vector *ephs; From 24110624fb42dd6750ac3e7c2da97721e5b13ba5 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 31 Aug 2023 01:46:52 +0200 Subject: [PATCH 38/57] various fixes --- qtest.pro | 6 +- src/back/backfuncs.h | 12 +-- src/back/backlasses.cpp | 18 +++-- src/back/backlasses.h | 18 +++-- src/back/ipolicyconfig.h | 158 +++++++++++++++++++++------------------ src/cont/contclasses.cpp | 19 ++--- src/cont/contclasses.h | 12 +-- src/qt/qtclasses.cpp | 31 +++++--- src/qtestmain.cpp | 4 +- 9 files changed, 152 insertions(+), 126 deletions(-) diff --git a/qtest.pro b/qtest.pro index b4af8be..d867f27 100644 --- a/qtest.pro +++ b/qtest.pro @@ -1,7 +1,7 @@ QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview -#QMAKE_LINK += clang++ -QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v - +QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v +LIBS += -LC:/capybara/libclang/x86_64-w64-mingw32/lib -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 +#"kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 DEFINES += DEBUG CONFIG += debug console diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h index 06adc7e..f66a295 100644 --- a/src/back/backfuncs.h +++ b/src/back/backfuncs.h @@ -1,11 +1,11 @@ -GUID NGuidToGUID(NGuid* guid) { +GUID NGuidToGUID(NGuid guid) { GUID msGuid = GUID(); - msGuid.Data1 = guid->data1; - msGuid.Data2 = guid->data2; - msGuid.Data3 = guid->data3; - msGuid.Data1 = guid->data1; + msGuid.Data1 = guid.data1; + msGuid.Data2 = guid.data2; + msGuid.Data3 = guid.data3; + msGuid.Data1 = guid.data1; for (int i = 0; i < 8; i++){ - msGuid.Data4[i] = guid->data4[i]; + msGuid.Data4[i] = guid.data4[i]; //log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); //log_debugcpp(print_as_binary(8, uint32_t, msGuid.Data4[i])); } diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 40abc4c..3b10f56 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -39,7 +39,6 @@ HRESULT EndpointVolumeCallback::QueryInterface(REFIID riid, VOID **ppvInterface) HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - //TODO: el default = objcopy frees? //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; //osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.freeData4(); //Could've made a function or = override to hide this within Nguid, but back in cont = bad. @@ -142,7 +141,7 @@ uint8_t Endpoint::getState(){ } -void Endpoint::setVolume(NGuid* guid, int channel, float volume) { +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) { @@ -152,7 +151,7 @@ void Endpoint::setVolume(NGuid* guid, int channel, float volume) { } } -void Endpoint::setMute(NGuid* guid, bool muted) { +void Endpoint::setMute(NGuid guid, bool muted) { GUID tempMsGuid = NGuidToGUID(guid); if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { /* TIP: Above */ }; } @@ -197,6 +196,10 @@ void Overseer::initCOMLibrary() { GUID tempGuid; if(FAILED(CoCreateGuid(&tempGuid))) { log_debugcpp("Failed to obtain GUID: " ); }; this->guid = GUIDToNGuid(&tempGuid); + + //if(FAILED(CoCreateInstance(__uuidof(CPolicyConfigClient), + // NULL, CLSCTX_ALL, __uuidof(IPolicyConfig10), (LPVOID *)&policyConfig))) {exit(-1);} + //TODO: Release lpguid? //TODO: Uninitialize COM } @@ -244,9 +247,9 @@ void Overseer::reloadEndpoints() { LPWSTR id = nullptr; for (unsigned int j = 0; j < numPlaybackEndpoints; j++){ - std::wstring test = playbackDevices.at(j)->getId(); + std::wstring eptId = playbackDevices.at(j)->getId(); temp->GetId(&id); - int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, test.c_str(), -987, id, -987, NULL, NULL, 0); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); if (comparison - 2 == 0) { log_wdebugcpp("ola defaul de " << i << " es " << id); playbackDevices.at(j)->setRoles((1 << i)); @@ -273,9 +276,8 @@ Overseer::Overseer(){ //int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); -//TODO guid -NGuid* Overseer::getGuid() { - return &guid; +NGuid Overseer::getGuid() { + return guid; } std::vector Overseer::getPlaybackEndpoints() { diff --git a/src/back/backlasses.h b/src/back/backlasses.h index f79de72..455faf1 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,16 +1,15 @@ #pragma once #define WIN32_LEAN_AND_MEAN -//done by qt by def #define UNICODE +#define _WIN32_WINNT 0x0A00 +#include -//#include "debug.h" -/* #include */ -/* #include */ -/* #include */ +//done by qt by def #define UNICODE #include #include #include #include +#include #include #include @@ -20,6 +19,8 @@ //#include #include #include +#include "ipolicyconfig.h" +#include #include "global.h" #include "contclasses.h" @@ -32,10 +33,10 @@ class Endpoint { Endpoint(IMMDevice* endpoint, uint64_t idx); uint64_t getIndex(); void setIndex(uint64_t idx); - void setVolume(NGuid* guid, int channel, float volume); + void setVolume(NGuid guid, int channel, float volume); uint32_t getChannelCount(); float getVolume(int channel); - void setMute(NGuid* guid, bool muted); + void setMute(NGuid guid, bool muted); bool getMute(); void setState(uint8_t state); uint8_t getState(); @@ -82,7 +83,7 @@ class Overseer { Overseer(); std::vector getPlaybackEndpoints(); void reloadEndpoints(); - NGuid* getGuid(); + NGuid getGuid(); //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); @@ -94,6 +95,7 @@ class Overseer { NGuid guid; unsigned int numPlaybackEndpoints; IMMDeviceEnumerator *deviceEnumerator; + IPolicyConfig *policyConfig; std::vector playbackDevices; void initCOMLibrary(); //IMMDeviceCollection *deviceCollection; diff --git a/src/back/ipolicyconfig.h b/src/back/ipolicyconfig.h index 203188c..2e10aab 100644 --- a/src/back/ipolicyconfig.h +++ b/src/back/ipolicyconfig.h @@ -9,6 +9,18 @@ #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") @@ -86,8 +98,8 @@ public: ); virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint( - __in PCWSTR wszDeviceId, - __in ERole eRole + PCWSTR wszDeviceId, + ERole eRole ); virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( @@ -96,85 +108,85 @@ public: ); }; -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: +/* 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 GetMixFormat( */ +/* PCWSTR, */ +/* WAVEFORMATEX ** */ +/* ); // not available on Windows 7, use method from IPolicyConfig */ - virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( - PCWSTR, - INT, - WAVEFORMATEX ** - ); +/* virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat( */ +/* PCWSTR, */ +/* INT, */ +/* WAVEFORMATEX ** */ +/* ); */ - virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat( - PCWSTR, - WAVEFORMATEX *, - 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 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 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 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 SetShareMode( */ +/* PCWSTR, */ +/* struct DeviceShareMode * */ +/* ); // not available on Windows 7, use method from IPolicyConfig */ - virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( - PCWSTR, - const PROPERTYKEY &, - PROPVARIANT * - ); +/* virtual HRESULT STDMETHODCALLTYPE GetPropertyValue( */ +/* PCWSTR, */ +/* const PROPERTYKEY &, */ +/* PROPVARIANT * */ +/* ); */ - virtual HRESULT STDMETHODCALLTYPE SetPropertyValue( - 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 SetDefaultEndpoint( */ +/* __in PCWSTR wszDeviceId, */ +/* __in ERole eRole */ +/* ); */ - virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( - PCWSTR, - INT - ); // not available on Windows 7, use method from IPolicyConfig -}; +/* virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility( */ +/* PCWSTR, */ +/* INT */ +/* ); // not available on Windows 7, use method from IPolicyConfig */ +/* }; */ diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index edfa18b..a6978b4 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -2,9 +2,6 @@ #include "contclasses.h" //TODO: pragma once -//TODO: ????? -Overseer OverseerHandler::os; - EndpointHandler::EndpointHandler(uint64_t idx) { //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); this->ep = osh->getPlaybackEndpoints().at(idx); @@ -39,13 +36,13 @@ uint64_t EndpointHandler::getIndex(){ /* * -1 for master volume */ -void EndpointHandler::setVolume(NGuid* guid, int channel, int value){ +void EndpointHandler::setVolume(NGuid guid, int channel, int value){ if (channel == AudioChannel::CHANNEL_MAIN) ep->setVolume(guid, channel, (float)value / 100); else ep->setVolume(guid, channel, (float)value / 100); } -void EndpointHandler::setMute(NGuid* guid, bool muted){ +void EndpointHandler::setMute(NGuid guid, bool muted){ ep->setMute(guid, muted); } @@ -79,8 +76,12 @@ EndpointHandler::~EndpointHandler() { delete ep; } +OverseerHandler::OverseerHandler() { + this->os = new Overseer(); +} + std::vector OverseerHandler::getPlaybackEndpoints() { - return this->os.getPlaybackEndpoints(); + return this->os->getPlaybackEndpoints(); } @@ -89,7 +90,7 @@ std::vector OverseerHandler::getEndpointHandlers(){ } uint64_t OverseerHandler::getPlaybackEndpointsCount(){ - return this->os.getPlaybackEndpoints().size(); + return this->os->getPlaybackEndpoints().size(); } void OverseerHandler::reloadEndpointHandlers(){ @@ -113,8 +114,8 @@ void OverseerHandler::reloadEndpointHandlers(){ //setEndpointHandlers(ephs); } -NGuid* OverseerHandler::getGuid() { - return this->os.getGuid(); +NGuid OverseerHandler::getGuid() { + return this->os->getGuid(); } void OverseerHandler::setEndpointHandlers(std::vector ephs){ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 0b8b733..bc3c96d 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -30,7 +30,7 @@ enum Roles { ROLE_CONSOLE = (1 << 0), ROLE_MULTIMEDIA = (1 << 1), ROLE_COMMUNICATIONS = (1 << 2), - ROLE_ALL = (7 << 0), + ROLE_ALL = 0x07, }; struct NGuid { @@ -79,8 +79,8 @@ public: uint8_t getState(); uint8_t getRoles(); - void setVolume(NGuid* guid, int channel, int value); - void setMute(NGuid* guid, bool muted); + void setVolume(NGuid guid, int channel, int value); + void setMute(NGuid guid, bool muted); void setState(uint8_t state); ~EndpointHandler(); @@ -94,16 +94,16 @@ private: class OverseerHandler { public: - //OverseerHandler(); + OverseerHandler(); void setEndpointHandlers(std::vector ephs); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); uint64_t getPlaybackEndpointsCount(); void reloadEndpointHandlers(); - NGuid* getGuid(); + NGuid getGuid(); private: - static Overseer os; + Overseer *os; std::vector endpointHandlers; //std::function updateFrontVolumeCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 9641e04..5f14192 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -8,7 +8,6 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("olaW"); if (parent == nullptr) { log_debugcpp("owo?"); } - defaultRolesCheckBoxes = { {Roles::ROLE_ALL, new QCheckBox()}, {Roles::ROLE_CONSOLE, new QCheckBox()}, @@ -68,14 +67,22 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare uint8_t assignedRoles = eph->getRoles(); defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(assignedRoles == Roles::ROLE_ALL ? Qt::Checked : Qt::Unchecked); - /* - * {ROLE_ALL, new QCheckBox(this)}, - * {ROLE_CONSOLE, new QCheckBox(this)}, - * {ROLE_MULTIMEDIA, new QCheckBox(this)}, - * {ROLE_COMMUNICATIONS, new QCheckBox(this)} - * }; - */ + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setText(STRING_ROLE_ALL); + + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setCheckState(assignedRoles & Roles::ROLE_CONSOLE ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setText(STRING_ROLE_CONSOLE); + + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setCheckState(assignedRoles & Roles::ROLE_MULTIMEDIA ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setText(STRING_ROLE_MULTIMEDIA); + + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setCheckState(assignedRoles & Roles::ROLE_COMMUNICATIONS ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setText(STRING_ROLE_COMMUNICATIONS); + + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setText(STRING_ROLE_ALL); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 3, 0); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 3, 1); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 3, 2); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), 3, 3); //Polling time @@ -97,14 +104,14 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); //TODO: el default = objcopy frees? - eph->getCallbackInfo()->caller = *osh->getGuid(); + eph->getCallbackInfo()->caller = osh->getGuid(); mainSlider->blockSignals(false); muteButton->blockSignals(false); }); timer->start(10); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 0); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 3, 1); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 4, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 4, 1); log_debugcpp("ENDPOINT_WIDGETED"); } @@ -230,7 +237,7 @@ void MainWindow::reloadEndpointWidgets() { size_t i = 0; for (; i < (osh->getEndpointHandlers().size()); i++) { log_debugcpp("EPWidget creation"); - osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = *osh->getGuid(); + osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET ews.push_back(epw); diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index 3b37996..cb32200 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -14,7 +14,7 @@ #include "qtclasses.h" #include "global.h" -OverseerHandler *osh = new OverseerHandler(); +OverseerHandler *osh = nullptr; QApplication* createApplication(int &argc, char *argv[]) { @@ -44,6 +44,8 @@ int main (int argc, char* argv[]) { startSingleInstanceServer("Mixer"); else exit(0); + osh = new OverseerHandler(); + //INIT CONT log_debugcpp("main init"); osh->reloadEndpointHandlers(); From 1a4692533d7c079936ecbc148914769fdf117521 Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 1 Sep 2023 19:30:04 +0200 Subject: [PATCH 39/57] wip: default callback --- assets/SoundVolumeView.exe | Bin 0 -> 203592 bytes bueno.bat | 2 + src/back/backlasses.cpp | 178 ++++++++++++++++++++++++++++++++++--- src/back/backlasses.h | 29 +++++- src/cont/contclasses.cpp | 8 ++ src/cont/contclasses.h | 5 +- src/qt/qtclasses.cpp | 23 ++++- 7 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 assets/SoundVolumeView.exe diff --git a/assets/SoundVolumeView.exe b/assets/SoundVolumeView.exe new file mode 100644 index 0000000000000000000000000000000000000000..4c7a7d2fe0e11b39fec51ffb8152ecd41e19e9c9 GIT binary patch literal 203592 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

~>seLx`ep|j5*LqYWqW(|Ths?p)KLc^ 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 XHi6$MX1&XX;<+8{CO*bT;E4YNbQI2I literal 0 HcmV?d00001 diff --git a/bueno.bat b/bueno.bat index 67fbd6a..2c8ac1c 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,2 +1,4 @@ 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 diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 3b10f56..ab68c51 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -66,6 +66,124 @@ HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify return S_OK; } +/* + * EndpointSituationCallback::EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices){ + * this->deviceEnumerator = deviceEnumerator; + * this->playbackDevices = playbackDevices; + * } + */ + +void EndpointSituationCallback::fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices){ + this->deviceEnumerator = deviceEnumerator; + this->playbackDevices = playbackDevices; +} + + +ULONG EndpointSituationCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointSituationCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointSituationCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IMMNotificationClient) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole role,LPCWSTR pwstrDeviceId) { + if (flow == EDataFlow::eCapture) return E_INVALIDARG; + + Roles nRole; + switch (role) { + case ERole::eConsole: + nRole = Roles::ROLE_CONSOLE; + break; + case ERole::eMultimedia: + nRole = Roles::ROLE_MULTIMEDIA; + break; + case ERole::eCommunications: + nRole = Roles::ROLE_COMMUNICATIONS; + break; + } + + osh + + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDeviceAdded(LPCWSTR pwstrDeviceId) { + + printf(" -->Added device\n"); + return S_OK; +}; + +HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { + + printf(" -->Removed device\n"); + return S_OK; +} + +HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { + /* + * char *pszState = "?????"; + * + * switch (dwNewState) + * { + * case DEVICE_STATE_ACTIVE: + * pszState = "ACTIVE"; + * break; + * case DEVICE_STATE_DISABLED: + * pszState = "DISABLED"; + * break; + * case DEVICE_STATE_NOTPRESENT: + * pszState = "NOTPRESENT"; + * break; + * case DEVICE_STATE_UNPLUGGED: + * pszState = "UNPLUGGED"; + * break; + * } + * + * printf(" -->New device state is DEVICE_STATE_%s (0x%8.8x)\n", + * pszState, dwNewState); + */ + + return S_OK; +} + +HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { + /* + * printf(" -->Changed device property " + * "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}#%d\n", + * key.fmtid.Data1, key.fmtid.Data2, key.fmtid.Data3, + * key.fmtid.Data4[0], key.fmtid.Data4[1], + * key.fmtid.Data4[2], key.fmtid.Data4[3], + * key.fmtid.Data4[4], key.fmtid.Data4[5], + * key.fmtid.Data4[6], key.fmtid.Data4[7], + * key.pid); + */ + return S_OK; +} + Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; this->idx = idx; @@ -168,7 +286,51 @@ uint8_t Endpoint::getRoles(){ return this->endpointRoles; } -void Endpoint::setRoles(uint8_t role){ +void Endpoint::setRoles(Roles role){ + //otro exe momento + STARTUPINFOEXW startupConfig; + PROCESS_INFORMATION processInfo; + SecureZeroMemory(&startupConfig, sizeof(STARTUPINFOEXW)); + SecureZeroMemory(&startupConfig.StartupInfo, sizeof(STARTUPINFOW)); + startupConfig.StartupInfo.cb = sizeof(STARTUPINFOEXW); + SecureZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); + + //const wchar_t* pCrutch = crutch.c_str(); + std::wstring command = L"SoundVolumeView.exe /SetDefault " + endpointId + L" "; + switch (role) { + case Roles::ROLE_ALL: + command += L"all"; + break; + case Roles::ROLE_CONSOLE: + command += std::to_wstring(0); + break; + case Roles::ROLE_MULTIMEDIA: + command += std::to_wstring(1); + break; + case Roles::ROLE_COMMUNICATIONS: + command += std::to_wstring(2); + break; + } + + if(CreateProcessW( + NULL, + (wchar_t*)command.c_str(), + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + (LPSTARTUPINFOW)&startupConfig, + &processInfo + ) == true) { + WaitForSingleObject(processInfo.hProcess, INFINITE ); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } +} + +void Endpoint::assignRoles(uint8_t role){ //todo: operador virtuoso uint8_t roles = endpointRoles | role; this->endpointRoles = roles; @@ -252,30 +414,24 @@ void Overseer::reloadEndpoints() { int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); if (comparison - 2 == 0) { log_wdebugcpp("ola defaul de " << i << " es " << id); - playbackDevices.at(j)->setRoles((1 << i)); + playbackDevices.at(j)->assignRoles((1 << i)); } //uint8_t debg = playbackDevices.at(j)->getRoles(); } } } -Overseer::Overseer(){ +Overseer::Overseer() { //: epsc(deviceEnumerator, playbackDevices){ //Initializing COM library log_debugcpp("Initializing Overseer"); initCOMLibrary(); //Obtaining playback endpoint collection on this point in time reloadEndpoints(); + this->epsc.fill(deviceEnumerator, playbackDevices); + if(FAILED(deviceEnumerator->RegisterEndpointNotificationCallback(((IMMNotificationClient*)&epsc)))) { log_debugcpp("when no enchufas......"); } } -//Overseer::int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint){ -//if (FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &endpointPtr))) -// return 1; -//return 0; -//} - -//int Overseer::getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); - NGuid Overseer::getGuid() { return guid; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 455faf1..db905a4 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -6,6 +6,7 @@ //done by qt by def #define UNICODE #include +#include #include #include #include @@ -41,7 +42,8 @@ class Endpoint { void setState(uint8_t state); uint8_t getState(); uint8_t getRoles(); - void setRoles(uint8_t role); + void setRoles(Roles role); + void assignRoles(uint8_t role); std::wstring getId(); std::wstring getName(); void setVolumeCallback(EndpointVolumeCallback *epc); @@ -77,6 +79,25 @@ class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { Endpoint* ep; }; +class EndpointSituationCallback : public IMMNotificationClient { + public: + //EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId); + HRESULT OnDeviceAdded(LPCWSTR pwstrDeviceId); + HRESULT OnDeviceRemoved(LPCWSTR pwstrDeviceId); + HRESULT OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); + HRESULT OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key); + + void fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); + private: + ULONG ref = 1; + IMMDeviceEnumerator *deviceEnumerator; + std::vector playbackDevices; +}; + class Overseer { //TODO singleton? public: @@ -84,6 +105,9 @@ class Overseer { std::vector getPlaybackEndpoints(); void reloadEndpoints(); NGuid getGuid(); + //void setEndpointStatusCallback(); + //void setEndpointStatusCallback(); + //~Overseer(); //int getDefaultPlaybackEndpoint(Endpoint** defaultEndpoint); //int getDefaultCaptureEndpoint(Endpoint** defaultEndpoint); @@ -95,7 +119,8 @@ class Overseer { NGuid guid; unsigned int numPlaybackEndpoints; IMMDeviceEnumerator *deviceEnumerator; - IPolicyConfig *policyConfig; + EndpointSituationCallback epsc; + //IPolicyConfig *policyConfig; std::vector playbackDevices; void initCOMLibrary(); //IMMDeviceCollection *deviceCollection; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index a6978b4..da1033c 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -70,6 +70,10 @@ uint8_t EndpointHandler::getRoles(){ return ep->getRoles(); } +void EndpointHandler::setRoles(Roles newRole){ + ep->setRoles(newRole); +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); epc->Release(); @@ -118,6 +122,10 @@ NGuid OverseerHandler::getGuid() { return this->os->getGuid(); } +void setChangeFrontDefaultsFunction(std::function changeFrontDefaults){ + this->changeFrontDefaults = changeFrontDefaults; +} + void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index bc3c96d..55d33d8 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -18,6 +18,7 @@ enum EndpointState { ENDPOINT_DISABLED = (1 << 1), ENDPOINT_NOTPRESENT = (1 << 2), ENDPOINT_UNPLUGGED = (1 << 3), + ENDPOINT_ALL = 0x0F }; enum Flows { @@ -78,6 +79,7 @@ public: bool getMute(); uint8_t getState(); uint8_t getRoles(); + void setRoles(Roles newRole); void setVolume(NGuid guid, int channel, int value); void setMute(NGuid guid, bool muted); @@ -95,6 +97,7 @@ class OverseerHandler { public: OverseerHandler(); + void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); void setEndpointHandlers(std::vector ephs); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); @@ -105,7 +108,7 @@ public: private: Overseer *os; std::vector endpointHandlers; - + std::function changeFrontDefaults; //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 5f14192..1140fca 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -67,18 +67,39 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare uint8_t assignedRoles = eph->getRoles(); defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(assignedRoles == Roles::ROLE_ALL ? Qt::Checked : Qt::Unchecked); + //todo duditas de & + defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(assignedRoles == Roles::ROLE_ALL ? true : false); defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setText(STRING_ROLE_ALL); defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setCheckState(assignedRoles & Roles::ROLE_CONSOLE ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setDisabled(assignedRoles & Roles::ROLE_CONSOLE ? true : false); defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE)->setText(STRING_ROLE_CONSOLE); defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setCheckState(assignedRoles & Roles::ROLE_MULTIMEDIA ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setDisabled(assignedRoles & Roles::ROLE_MULTIMEDIA ? true : false); defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA)->setText(STRING_ROLE_MULTIMEDIA); defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setCheckState(assignedRoles & Roles::ROLE_COMMUNICATIONS ? Qt::Checked : Qt::Unchecked); + defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setDisabled(assignedRoles & Roles::ROLE_COMMUNICATIONS ? true : false); defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS)->setText(STRING_ROLE_COMMUNICATIONS); + + connect(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_ALL); + //todo: bloquiar pto + }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_CONSOLE); + //todo: bloquiar pto + }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_MULTIMEDIA); + //todo: bloquiar pto + }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), &QCheckBox::stateChanged,[this] { + this->eph->setRoles(Roles::ROLE_COMMUNICATIONS); + //todo: bloquiar pto + }); - defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setText(STRING_ROLE_ALL); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 3, 0); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 3, 1); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 3, 2); From f620a0575d298bbe2ec327a880959956d21c7ae0 Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 1 Sep 2023 21:36:07 +0200 Subject: [PATCH 40/57] wip: setenabled crash to fix --- src/back/backlasses.cpp | 16 +++++------ src/back/backlasses.h | 1 + src/cont/contclasses.cpp | 28 +++++++++++++++++++- src/cont/contclasses.h | 21 ++++++++++----- src/qt/qtclasses.cpp | 57 ++++++++++++++++++++++++++++++++++++++++ src/qt/qtclasses.h | 5 ++-- 6 files changed, 111 insertions(+), 17 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index ab68c51..083d60c 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -125,21 +125,17 @@ HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole nRole = Roles::ROLE_COMMUNICATIONS; break; } - - osh + std::wstring wstringEndpointId = pwstrDeviceId; + osh->changeFrontDefaultsCallback(nRole, wstringEndpointId); return S_OK; } HRESULT EndpointSituationCallback::OnDeviceAdded(LPCWSTR pwstrDeviceId) { - - printf(" -->Added device\n"); return S_OK; }; -HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { - - printf(" -->Removed device\n"); +HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } @@ -331,11 +327,15 @@ void Endpoint::setRoles(Roles role){ } void Endpoint::assignRoles(uint8_t role){ - //todo: operador virtuoso uint8_t roles = endpointRoles | role; this->endpointRoles = roles; } +void Endpoint::removeRoles(uint8_t role){ + uint8_t roles = endpointRoles ^ role; + this->endpointRoles = roles; +} + Endpoint::~Endpoint(){ log_debugcpp("murio endpoint-san uwu"); properties->Release(); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index db905a4..d8b0d2b 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -44,6 +44,7 @@ class Endpoint { uint8_t getRoles(); void setRoles(Roles role); void assignRoles(uint8_t role); + void removeRoles(uint8_t role); std::wstring getId(); std::wstring getName(); void setVolumeCallback(EndpointVolumeCallback *epc); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index da1033c..abedbfd 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -17,6 +17,16 @@ EndpointHandler::EndpointHandler(uint64_t idx) { } } +/* 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; } @@ -50,6 +60,10 @@ std::wstring EndpointHandler::getName(){ return ep->getName(); } +std::wstring EndpointHandler::getId(){ + return ep->getId(); +} + float EndpointHandler::getVolume(int channel){ return ep->getVolume(channel); } @@ -74,6 +88,14 @@ void EndpointHandler::setRoles(Roles newRole){ ep->setRoles(newRole); } +void EndpointHandler::assignRoles(Roles newRole){ + ep->assignRoles((uint8_t)newRole); +} + +void EndpointHandler::removeRoles(Roles newRole){ + ep->removeRoles((uint8_t)newRole); +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); epc->Release(); @@ -122,10 +144,14 @@ NGuid OverseerHandler::getGuid() { return this->os->getGuid(); } -void setChangeFrontDefaultsFunction(std::function changeFrontDefaults){ +void OverseerHandler::setChangeFrontDefaultsFunction(std::function changeFrontDefaults){ this->changeFrontDefaults = changeFrontDefaults; } +void OverseerHandler::changeFrontDefaultsCallback(Roles role, std::wstring endpointId) { + this->changeFrontDefaults(role, endpointId); +} + void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 55d33d8..7ea9f6e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -62,11 +62,11 @@ class EndpointHandler { public: EndpointHandler(uint64_t idx); - //TODO: get(); - Endpoint *ep = nullptr; - EndpointVolumeCallback *epc = nullptr; + //these two, currently unused. If I use them, I should feel bad. + //EndpointVolumeCallback* getEndpointVolumeCallback(); + //Endpoint* getEndpoint(); + //std::wstring epName; - BackEndpointVolumeCallbackInfo* getCallbackInfo(); uint32_t getChannelCount(); @@ -75,11 +75,15 @@ public: void setVolume(int channel, float volume); std::wstring getName(); + std::wstring getId(); + float getVolume(int channel); bool getMute(); uint8_t getState(); uint8_t getRoles(); void setRoles(Roles newRole); + void assignRoles(Roles newRole); + void removeRoles(Roles newRole); void setVolume(NGuid guid, int channel, int value); void setMute(NGuid guid, bool muted); @@ -88,6 +92,8 @@ public: ~EndpointHandler(); private: uint64_t idx; + Endpoint *ep = nullptr; + EndpointVolumeCallback *epc = nullptr; BackEndpointVolumeCallbackInfo callbackInfo; //QSlider *slidy; }; @@ -97,7 +103,9 @@ class OverseerHandler { public: OverseerHandler(); - void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); + void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); + void changeFrontDefaultsCallback(Roles role, std::wstring endpointId); + void setEndpointHandlers(std::vector ephs); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); @@ -108,7 +116,8 @@ public: private: Overseer *os; std::vector endpointHandlers; - std::function changeFrontDefaults; + std::function changeFrontDefaults; + //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 1140fca..2543e1e 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -192,6 +192,10 @@ void EndpointWidget::updateMainVolume(int newValue){ * } */ +EndpointHandler* EndpointWidget::getEndpointHandler(){ + return this->eph; +} + void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; } @@ -228,8 +232,61 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { trayIcon->show(); trayIcon->setToolTip(STRING_TITLE); trayIcon->setContextMenu(trayIconMenu); + //todo: ayo... QString as = trayIcon->toolTip(); connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); + + + osh->setChangeFrontDefaultsFunction([this](Roles role, std::wstring endpointId) { + + for (auto epw : this->ews) { + if (epw->getEndpointHandler()->getId() == endpointId) { + //not necessary to keep endpointState flags up to date right now, but updating it will allow for later config files / profiles + epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); + epw->getEndpointHandler()->assignRoles(role); + epw->defaultRolesCheckBoxes.at(role)->setCheckState(Qt::Checked); + //epw->defaultRolesCheckBoxes.at(role)->setDisabled(true); + epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); + + if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(true); + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(Qt::Checked); + //epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(true); + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(false); + } + + /* + * switch (role) { + * case Roles::ROLE_CONSOLE: + * epw->getEndpointHandler()->assignRoles(role); + * break; + * case Roles::ROLE_MULTIMEDIA: + * break; + * case Roles::ROLE_COMMUNICATIONS: + * break; + * + * } + */ + } else { + if (epw->getEndpointHandler()->getRoles() & role) { + epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); + epw->getEndpointHandler()->removeRoles(role); + epw->defaultRolesCheckBoxes.at(role)->setCheckState(Qt::Unchecked); + //epw->defaultRolesCheckBoxes.at(role)->setDisabled(false); + epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); + + if (!epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->isEnabled()) { + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(true); + //epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(false); + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(Qt::Unchecked); + epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(false); + + } + + } + } + } + }); } void MainWindow::closeEvent(QCloseEvent *event) { diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 4082fc7..062c308 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -62,8 +62,8 @@ class EndpointWidget : public QWidget { public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); - //TODO: get(); - EndpointHandler* eph; + + EndpointHandler* getEndpointHandler(); void setIndex(uint64_t idx); uint64_t getIndex(); @@ -90,6 +90,7 @@ public slots: void updateMute(int checked); private: + EndpointHandler* eph; size_t defaultRolesVectorSize = 4; QTimer* timer = nullptr; uint64_t idx; From fc63875851737b0d22ef514248b26adca0794749 Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 5 Sep 2023 19:42:30 +0200 Subject: [PATCH 41/57] defaults working as expected. bruh --- src/back/backlasses.cpp | 8 +++- src/qt/qtclasses.cpp | 84 +++++++++++++++++++++++++---------------- src/qt/qtclasses.h | 13 ++++++- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 083d60c..13ae38e 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -295,7 +295,13 @@ void Endpoint::setRoles(Roles role){ std::wstring command = L"SoundVolumeView.exe /SetDefault " + endpointId + L" "; switch (role) { case Roles::ROLE_ALL: - command += L"all"; + /* + * 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"; + */ + command += L"0 1"; break; case Roles::ROLE_CONSOLE: command += std::to_wstring(0); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 2543e1e..0bb0f08 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,5 +1,24 @@ #include "qtclasses.h" + +bool ExtendedCheckBox::event(QEvent* ev) { + if (ev->type() == QEvent::User) { + this->blockSignals(true); + if (this->isEnabled()) { + this->setCheckState(Qt::Checked); + this->setDisabled(true); + } else { + this->setDisabled(false); + this->setCheckState(Qt::Unchecked); + } + this->blockSignals(false); + return true; + } + // Make sure the rest of events are handled + return QCheckBox::event(ev); +} + + EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ this->idx = idx; this->eph = eph; @@ -9,10 +28,10 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare if (parent == nullptr) { log_debugcpp("owo?"); } defaultRolesCheckBoxes = { - {Roles::ROLE_ALL, new QCheckBox()}, - {Roles::ROLE_CONSOLE, new QCheckBox()}, - {Roles::ROLE_MULTIMEDIA, new QCheckBox()}, - {Roles::ROLE_COMMUNICATIONS, new QCheckBox()} + {Roles::ROLE_ALL, new ExtendedCheckBox()}, + {Roles::ROLE_CONSOLE, new ExtendedCheckBox()}, + {Roles::ROLE_MULTIMEDIA, new ExtendedCheckBox()}, + {Roles::ROLE_COMMUNICATIONS, new ExtendedCheckBox()} }; muteButton = new QCheckBox(); @@ -32,11 +51,12 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider->setValue((int)volume); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); + //todo: parent mainMuteLayout = new QGridLayout(); - layout->addLayout(mainMuteLayout, 0, 0); + layout->addLayout(mainMuteLayout, 0, 0, 2, 1, Qt::AlignLeft | Qt::AlignBottom); mainMuteLayout->addWidget(mainLabel, 0, 0); mainMuteLayout->addWidget(muteButton, 0, 1); - layout->addWidget(mainSlider, 0, 1); + layout->addWidget(mainSlider, 0, 2, 1, 2, Qt::AlignLeft | Qt::AlignBottom); //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); @@ -55,8 +75,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare tmpLb->setText(QString::number(volume)); this->channelSliders.push_back(tmp); this->channelLabels.push_back(tmpLb); - layout->addWidget(tmp, 1, i); - layout->addWidget(tmpLb, 2, i); + layout->addWidget(tmp, 2, i, 1, 2, Qt::AlignLeft | Qt::AlignTop); + layout->addWidget(tmpLb, 3, i, 1, 2, Qt::AlignLeft | Qt::AlignTop); //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func //this causes channel bar desync when back -> front. blocksignals below fix it. huh. connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ @@ -95,19 +115,20 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->eph->setRoles(Roles::ROLE_MULTIMEDIA); //todo: bloquiar pto }); + connect(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), &QCheckBox::stateChanged,[this] { this->eph->setRoles(Roles::ROLE_COMMUNICATIONS); //todo: bloquiar pto - }); + }); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 3, 0); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 3, 1); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 3, 2); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), 3, 3); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 5, 0); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 5, 1); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 5, 2); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), 5, 3); //Polling time - timer = new QTimer(this); + timer = new QTimer(); connect(timer, &QTimer::timeout, [this, eph](){ //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. const float roundingFactor = 0.005; @@ -130,9 +151,11 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare muteButton->blockSignals(false); }); timer->start(10); - - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 4, 0); - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 4, 1); + //todo parent? + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 4, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 6, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), 6, 1); log_debugcpp("ENDPOINT_WIDGETED"); } @@ -212,6 +235,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { trayIcon = new QSystemTrayIcon(); trayIconMenu = new QMenu(); trayIconMenuQuit = new QAction(STRING_QUIT); + changeDefaultCheckboxEnablement = new QEvent(QEvent::User); + changeDefaultCheckboxEnablement->setAccepted(true); widget->setLayout(layout); setCentralWidget(widget); @@ -244,15 +269,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { //not necessary to keep endpointState flags up to date right now, but updating it will allow for later config files / profiles epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); epw->getEndpointHandler()->assignRoles(role); - epw->defaultRolesCheckBoxes.at(role)->setCheckState(Qt::Checked); - //epw->defaultRolesCheckBoxes.at(role)->setDisabled(true); - epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); + epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); + //epw->defaultRolesCheckBoxes.at(role)->postEnableChange(); if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(true); - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(Qt::Checked); - //epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(true); - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); } /* @@ -269,19 +291,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { */ } else { if (epw->getEndpointHandler()->getRoles() & role) { + if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); + } + epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); epw->getEndpointHandler()->removeRoles(role); - epw->defaultRolesCheckBoxes.at(role)->setCheckState(Qt::Unchecked); //epw->defaultRolesCheckBoxes.at(role)->setDisabled(false); epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); - - if (!epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->isEnabled()) { - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(true); - //epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setDisabled(false); - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(Qt::Unchecked); - epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->blockSignals(false); - - } + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); } } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 062c308..df21c9b 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -57,6 +57,16 @@ * }; */ + +class ExtendedCheckBox : public QCheckBox { + Q_OBJECT + + +public: + bool event(QEvent* ev) override; + +}; + class EndpointWidget : public QWidget { Q_OBJECT @@ -76,7 +86,7 @@ public: std::vector channelLabels; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; - std::map defaultRolesCheckBoxes; + std::map defaultRolesCheckBoxes; //void updateMainVolume(float newValue); //void updateVolume(uint32_t channel, float newValue); @@ -127,6 +137,7 @@ private: QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; QAction *trayIconMenuQuit; + QEvent* changeDefaultCheckboxEnablement; //public slots: // void setEndpointHandlers(std::vector *ephs); From a6a052f34876ef28154e937e949acd231c0427bf Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 6 Sep 2023 17:57:15 +0200 Subject: [PATCH 42/57] endpoint add/remove foundational work --- src/back/backlasses.cpp | 62 +++++++++++++++-------- src/back/backlasses.h | 6 +-- src/cont/contclasses.cpp | 18 ++++--- src/cont/contclasses.h | 2 +- src/qt/qtclasses.cpp | 105 +++++++++++++++++---------------------- src/qt/qtclasses.h | 6 +-- 6 files changed, 103 insertions(+), 96 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 13ae38e..d5d4906 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -183,16 +183,15 @@ HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; this->idx = idx; - //if(FAILED()) {}; - DWORD tempState = 0; - if(FAILED(endpoint->GetState(&tempState))) {exit(-1);}; - this->endpointState = tempState; - - if (tempState == DEVICE_STATE_ACTIVE) { + + //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas + if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-1);}; + + if (this->endpointState == DEVICE_STATE_ACTIVE) { if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { /* log_debugcpp("si"); */ }; if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {};/* log_debugcpp("get channel count fail"); */ - } else channelCount = 0; + } //todo:: atexit into exit Gather ID LPWSTR tempString = nullptr; @@ -250,7 +249,7 @@ void Endpoint::setState(uint8_t state){ this->endpointState = state; } -uint8_t Endpoint::getState(){ +size_t Endpoint::getState(){ return this->endpointState; } @@ -291,17 +290,34 @@ void Endpoint::setRoles(Roles role){ startupConfig.StartupInfo.cb = sizeof(STARTUPINFOEXW); SecureZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); - //const wchar_t* pCrutch = crutch.c_str(); std::wstring command = L"SoundVolumeView.exe /SetDefault " + endpointId + L" "; + std::wstring troublePair = L"0"; switch (role) { case Roles::ROLE_ALL: /* - * 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"; + * 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... */ - command += L"0 1"; + troublePair = command + troublePair; + if(CreateProcessW( + NULL, + (wchar_t*)troublePair.c_str(), + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, + NULL, + NULL, + (LPSTARTUPINFOW)&startupConfig, + &processInfo + ) == true) { + WaitForSingleObject(processInfo.hProcess, INFINITE ); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + command += L"2"; break; case Roles::ROLE_CONSOLE: command += std::to_wstring(0); @@ -375,16 +391,18 @@ void Overseer::initCOMLibrary() { void Overseer::reloadEndpoints() { IMMDeviceCollection *deviceCollection; // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE , &deviceCollection) )) + if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) )) { log_debugcpp("si"); }; - - //Counting them + /* + * Counting them + */ if(FAILED(deviceCollection->GetCount(&numPlaybackEndpoints))) { log_debugcpp("si");}; if(numPlaybackEndpoints == 0) { log_debugcpp("si"); }; - - //Retrieving actual endpoints and storing them on their own class + /* + * Retrieving actual endpoints and storing them on their own class + */ IMMDevice *temp; for (unsigned int i = 0; i < numPlaybackEndpoints; i++){ if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; @@ -396,8 +414,10 @@ void Overseer::reloadEndpoints() { deviceCollection->Release(); - //Discerning default endpoints per role - //order: console, multimedia, communications + /* + * Discerning default endpoints per role + * order: console, multimedia, communications + */ for(int i = 0; i < ERole_enum_count; i++){ ERole val; switch(i) { diff --git a/src/back/backlasses.h b/src/back/backlasses.h index d8b0d2b..469839f 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -40,7 +40,7 @@ class Endpoint { void setMute(NGuid guid, bool muted); bool getMute(); void setState(uint8_t state); - uint8_t getState(); + size_t getState(); uint8_t getRoles(); void setRoles(Roles role); void assignRoles(uint8_t role); @@ -52,13 +52,13 @@ class Endpoint { ~Endpoint(); private: - uint32_t channelCount; + uint32_t channelCount = 0; IMMDevice* endpoint; IAudioEndpointVolume *endpointVolume ; IPropertyStore *properties; std::wstring friendlyName; std::wstring endpointId; - uint8_t endpointState; + unsigned long endpointState; uint8_t endpointRoles = 0; uint64_t idx; // LPWSTR endpointID = NULL; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index abedbfd..28864c5 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -7,13 +7,15 @@ EndpointHandler::EndpointHandler(uint64_t idx) { this->ep = osh->getPlaybackEndpoints().at(idx); epc = new EndpointVolumeCallback(ep); //epName = ep->getName(); - ep->setVolumeCallback(epc); - callbackInfo.muted = this->getMute(); - callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); - callbackInfo.channels = this->getChannelCount(); - callbackInfo.channelVolumes.resize(this->callbackInfo.channels); - for(uint32_t i = 0; i < this->getChannelCount(); i++){ - callbackInfo.channelVolumes[i] = this->getVolume(i); + if (this->ep->getState() == EndpointState::ENDPOINT_ACTIVE) { + callbackInfo.muted = this->getMute(); + callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); + callbackInfo.channels = this->getChannelCount(); + ep->setVolumeCallback(epc); + callbackInfo.channelVolumes.resize(this->callbackInfo.channels); + for(uint32_t i = 0; i < this->getChannelCount(); i++){ + callbackInfo.channelVolumes[i] = this->getVolume(i); + } } } @@ -72,7 +74,7 @@ bool EndpointHandler::getMute(){ return ep->getMute(); } -uint8_t EndpointHandler::getState(){ +size_t EndpointHandler::getState(){ return ep->getState(); } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 7ea9f6e..7ff6882 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -79,7 +79,7 @@ public: float getVolume(int channel); bool getMute(); - uint8_t getState(); + size_t getState(); uint8_t getRoles(); void setRoles(Roles newRole); void assignRoles(Roles newRole); diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 0bb0f08..8bea983 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -33,10 +33,19 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare {Roles::ROLE_MULTIMEDIA, new ExtendedCheckBox()}, {Roles::ROLE_COMMUNICATIONS, new ExtendedCheckBox()} }; - + + /* + * Mute, main slider and label setup + */ muteButton = new QCheckBox(); mainLabel = new QLabel(QString::fromStdWString(eph->getName())); mainSlider = new QSlider(Qt::Horizontal); + + if (this->eph->getState() != EndpointState::ENDPOINT_ACTIVE) { + layout->addWidget(mainLabel, 0, 0); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); + return; + } //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); mainSlider->setFocusPolicy(Qt::StrongFocus); @@ -44,7 +53,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider->setTickInterval(5); mainSlider->setSingleStep(1); mainSlider->setRange(0,100); - + muteButton->setCheckState((eph->getMute() == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(AudioChannel::CHANNEL_MAIN) * 100; @@ -63,6 +72,9 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare connect(muteButton, &QCheckBox::stateChanged, this, (&EndpointWidget::updateMute)); +/* + * Channel sliders setup + */ for(uint32_t i = 0; i < eph->getChannelCount(); i++){ QSlider* tmp = new QSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); @@ -84,6 +96,10 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->channelLabels.at(i)->setText(QString::number(newValue)); }); } + + /* + * Role ExtendedCheckBoxes setup + */ uint8_t assignedRoles = eph->getRoles(); defaultRolesCheckBoxes.at(Roles::ROLE_ALL)->setCheckState(assignedRoles == Roles::ROLE_ALL ? Qt::Checked : Qt::Unchecked); @@ -105,29 +121,28 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare connect(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), &QCheckBox::stateChanged,[this] { this->eph->setRoles(Roles::ROLE_ALL); - //todo: bloquiar pto }); connect(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), &QCheckBox::stateChanged,[this] { this->eph->setRoles(Roles::ROLE_CONSOLE); - //todo: bloquiar pto }); connect(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), &QCheckBox::stateChanged,[this] { this->eph->setRoles(Roles::ROLE_MULTIMEDIA); - //todo: bloquiar pto }); connect(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), &QCheckBox::stateChanged,[this] { this->eph->setRoles(Roles::ROLE_COMMUNICATIONS); - //todo: bloquiar pto }); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 5, 0); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 5, 1); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 5, 2); layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), 5, 3); + +/* ----------------------------------------------------------- */ - - //Polling time + /* + * EndpointVolume Polling time + */ timer = new QTimer(); connect(timer, &QTimer::timeout, [this, eph](){ //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. @@ -151,6 +166,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare muteButton->blockSignals(false); }); timer->start(10); + //todo parent? layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 4, 0); @@ -159,20 +175,6 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("ENDPOINT_WIDGETED"); } -/* - * void EndpointWidget::updateMute(bool muted){ - * //TIP: Blocksignals here to diagnose slider visuals locking when playing DJ with external volume bar. Functionality is restored when mute checkbox is clicked. - * //this->blockSignals(true); - * this->muteButton->blockSignals(true); - * - * //this->eph->setMute(osh->getGuid(), muted); - * this->muteButton->setChecked(muted); - * this->muteButton->setText(this->eph->getMute() ? STRING_UNMUTE : STRING_MUTE); - * this->muteButton->blockSignals(false); - * //this->blockSignals(false); - * } - */ - void EndpointWidget::updateMute(int checked){ bool muted = (checked == 2 ? true : false); this->eph->setMute(osh->getGuid(), muted); @@ -180,7 +182,6 @@ void EndpointWidget::updateMute(int checked){ } void EndpointWidget::updateMainVolume(int newValue){ - //QObject* obj = sender(); this->eph->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } @@ -246,7 +247,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { reloadEndpointWidgets(); - //Tray Icon + /* + * Tray Icon code + */ trayIconMenu->addSeparator(); trayIconMenu->addAction(trayIconMenuQuit); connect(trayIconMenuQuit, &QAction::triggered, qApp, &QCoreApplication::quit); @@ -257,14 +260,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { trayIcon->show(); trayIcon->setToolTip(STRING_TITLE); trayIcon->setContextMenu(trayIconMenu); - //todo: ayo... - QString as = trayIcon->toolTip(); connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); - - + + /* + * Set of function callback definitinos for EndpointSituationCallback + */ osh->setChangeFrontDefaultsFunction([this](Roles role, std::wstring endpointId) { - for (auto epw : this->ews) { + /* + * Is this the new default endpoint? + */ if (epw->getEndpointHandler()->getId() == endpointId) { //not necessary to keep endpointState flags up to date right now, but updating it will allow for later config files / profiles epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); @@ -272,39 +277,32 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); //epw->defaultRolesCheckBoxes.at(role)->postEnableChange(); - + /* + * And were you THE default? + */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); } - - /* - * switch (role) { - * case Roles::ROLE_CONSOLE: - * epw->getEndpointHandler()->assignRoles(role); - * break; - * case Roles::ROLE_MULTIMEDIA: - * break; - * case Roles::ROLE_COMMUNICATIONS: - * break; - * - * } - */ - } else { - if (epw->getEndpointHandler()->getRoles() & role) { + /* + * Are you the dethroned king? + */ + } else if (epw->getEndpointHandler()->getRoles() & role) { + /* + * And were you THE default up until now? + */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); } epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); + //Same as before. ini-san will come... epw->getEndpointHandler()->removeRoles(role); - //epw->defaultRolesCheckBoxes.at(role)->setDisabled(false); epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); } } - } - }); + }); } void MainWindow::closeEvent(QCloseEvent *event) { @@ -342,16 +340,5 @@ void MainWindow::reloadEndpointWidgets() { } layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); - - /* - * osh->setFrontVolumeCallback([this](uint64_t device, uint32_t channel, float value) { - * if (device < ews.size()) - * ews.at(device)->updateVolume(channel, value); - * }); - * osh->setFrontMuteCallback([this](uint64_t device, bool muted) { - * if (device < ews.size()) - * ews.at(device)->updateMute(muted); - * }); - */ } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index df21c9b..db9917a 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -59,14 +59,12 @@ class ExtendedCheckBox : public QCheckBox { - Q_OBJECT - - + Q_OBJECT public: bool event(QEvent* ev) override; - }; + class EndpointWidget : public QWidget { Q_OBJECT From 04da76c021e59e86fe36ffe8ab61f39e07fb33a9 Mon Sep 17 00:00:00 2001 From: Hane Date: Wed, 6 Sep 2023 19:59:55 +0200 Subject: [PATCH 43/57] enable/disable/add/remove groundwork done --- src/back/backlasses.cpp | 60 ++++++++++++++++++++--------------------- src/back/backlasses.h | 1 - src/qt/qtclasses.cpp | 59 +++++++++++++++++++++------------------- src/qt/qtclasses.h | 4 +++ 4 files changed, 66 insertions(+), 58 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index d5d4906..c726753 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -71,14 +71,14 @@ HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify * this->deviceEnumerator = deviceEnumerator; * this->playbackDevices = playbackDevices; * } + * */ - +//todo: not on construct since it expects them to already exist; smells like refactor! void EndpointSituationCallback::fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices){ this->deviceEnumerator = deviceEnumerator; this->playbackDevices = playbackDevices; } - ULONG EndpointSituationCallback::AddRef(){ return InterlockedIncrement(&ref); } @@ -132,37 +132,31 @@ HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole } HRESULT EndpointSituationCallback::OnDeviceAdded(LPCWSTR pwstrDeviceId) { + log_wdebugcpp(L"ayo we eventing za adin" << pwstrDeviceId); return S_OK; }; HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { + log_wdebugcpp(L"ayo we eventing za rmovin" << pwstrDeviceId); return S_OK; } -HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { - /* - * char *pszState = "?????"; - * - * switch (dwNewState) - * { - * case DEVICE_STATE_ACTIVE: - * pszState = "ACTIVE"; - * break; - * case DEVICE_STATE_DISABLED: - * pszState = "DISABLED"; - * break; - * case DEVICE_STATE_NOTPRESENT: - * pszState = "NOTPRESENT"; - * break; - * case DEVICE_STATE_UNPLUGGED: - * pszState = "UNPLUGGED"; - * break; - * } - * - * printf(" -->New device state is DEVICE_STATE_%s (0x%8.8x)\n", - * pszState, dwNewState); - */ +HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { + switch (dwNewState){ + case DEVICE_STATE_ACTIVE: + break; + case DEVICE_STATE_DISABLED: + + break; + case DEVICE_STATE_NOTPRESENT: + + break; + case DEVICE_STATE_UNPLUGGED: + + break; + } + return S_OK; } @@ -184,6 +178,10 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ this->endpoint = ep; this->idx = idx; + /* + * It can't multiflag, it's that stupid. MS momento. + * Only shows most relevant flag according to MS, i.e. 0110 sends 0010 + */ //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-1);}; @@ -203,7 +201,10 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ endpoint->OpenPropertyStore(STGM_READ, &properties); PROPVARIANT pv; properties->GetValue(PKEY_Device_FriendlyName , &pv); - friendlyName = std::wstring(pv.pwszVal); + if (pv.pwszVal == nullptr) + friendlyName = L"Unnamed Not Present Endpoint"; + else + friendlyName = std::wstring(pv.pwszVal); } void Endpoint::setIndex(uint64_t idx){ @@ -214,7 +215,6 @@ uint64_t Endpoint::getIndex(){ return idx; } - std::wstring Endpoint::getName(){ return friendlyName; } @@ -253,14 +253,13 @@ size_t Endpoint::getState(){ return this->endpointState; } - void Endpoint::setVolume(NGuid guid, int channel, float volume) { //TIP: There used to be log messages here. Now, it's a ghost town. GUID tempMsGuid = NGuidToGUID(guid); if (channel == AudioChannel::CHANNEL_MAIN) { if(FAILED(endpointVolume->SetMasterVolumeLevelScalar(volume, &tempMsGuid))) {}; } else { - if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) { }; + if(FAILED(endpointVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) {}; } } @@ -391,7 +390,8 @@ void Overseer::initCOMLibrary() { void Overseer::reloadEndpoints() { IMMDeviceCollection *deviceCollection; // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection) )) + // NOTPRESENT shows a lot of garbage, unnamed devices. + if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection) )) { log_debugcpp("si"); }; /* diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 469839f..44407a0 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -61,7 +61,6 @@ class Endpoint { unsigned long endpointState; uint8_t endpointRoles = 0; uint64_t idx; - // LPWSTR endpointID = NULL; }; class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 8bea983..38b0e44 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,6 +1,7 @@ #include "qtclasses.h" + bool ExtendedCheckBox::event(QEvent* ev) { if (ev->type() == QEvent::User) { this->blockSignals(true); @@ -20,26 +21,27 @@ bool ExtendedCheckBox::event(QEvent* ev) { EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ + //todo: based on qgridlayout, name+mute should be its own widget, same with channels this->idx = idx; this->eph = eph; - layout = new QGridLayout(); - this->setLayout(layout); - log_debugcpp("olaW"); + layout = new QGridLayout(this); + //this->setLayout(layout); + log_debugcpp("epw main layout parent: "<< layout->parent()); if (parent == nullptr) { log_debugcpp("owo?"); } defaultRolesCheckBoxes = { - {Roles::ROLE_ALL, new ExtendedCheckBox()}, - {Roles::ROLE_CONSOLE, new ExtendedCheckBox()}, - {Roles::ROLE_MULTIMEDIA, new ExtendedCheckBox()}, - {Roles::ROLE_COMMUNICATIONS, new ExtendedCheckBox()} + {Roles::ROLE_ALL, new ExtendedCheckBox(this)}, + {Roles::ROLE_CONSOLE, new ExtendedCheckBox(this)}, + {Roles::ROLE_MULTIMEDIA, new ExtendedCheckBox(this)}, + {Roles::ROLE_COMMUNICATIONS, new ExtendedCheckBox(this)} }; /* * Mute, main slider and label setup */ - muteButton = new QCheckBox(); - mainLabel = new QLabel(QString::fromStdWString(eph->getName())); - mainSlider = new QSlider(Qt::Horizontal); + muteButton = new QCheckBox(this); + mainLabel = new QLabel(QString::fromStdWString(eph->getName()), this); + mainSlider = new QSlider(Qt::Horizontal, this); if (this->eph->getState() != EndpointState::ENDPOINT_ACTIVE) { layout->addWidget(mainLabel, 0, 0); @@ -48,6 +50,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } //muteButton->setStyleSheet("background-color: #A3C1DA; color: red"); + mainSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mainSlider->setFocusPolicy(Qt::StrongFocus); mainSlider->setTickPosition(QSlider::TicksBothSides); mainSlider->setTickInterval(5); @@ -60,12 +63,11 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider->setValue((int)volume); log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); - //todo: parent - mainMuteLayout = new QGridLayout(); - layout->addLayout(mainMuteLayout, 0, 0, 2, 1, Qt::AlignLeft | Qt::AlignBottom); - mainMuteLayout->addWidget(mainLabel, 0, 0); - mainMuteLayout->addWidget(muteButton, 0, 1); - layout->addWidget(mainSlider, 0, 2, 1, 2, Qt::AlignLeft | Qt::AlignBottom); + //tip: would need to be new widget with layout in it + //mainMuteLayout = new QGridLayout(); + layout->addWidget(mainLabel, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(muteButton, 0, 1, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(mainSlider, 0, 2, 1, 2); //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); @@ -78,6 +80,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare for(uint32_t i = 0; i < eph->getChannelCount(); i++){ QSlider* tmp = new QSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); + tmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); tmp->setTickInterval(5); tmp->setSingleStep(1); tmp->setRange(0,100); @@ -87,8 +90,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare tmpLb->setText(QString::number(volume)); this->channelSliders.push_back(tmp); this->channelLabels.push_back(tmpLb); - layout->addWidget(tmp, 2, i, 1, 2, Qt::AlignLeft | Qt::AlignTop); - layout->addWidget(tmpLb, 3, i, 1, 2, Qt::AlignLeft | Qt::AlignTop); + layout->addWidget(tmp, 2, i); + layout->addWidget(tmpLb, 3, i); //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func //this causes channel bar desync when back -> front. blocksignals below fix it. huh. connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ @@ -329,16 +332,18 @@ void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { void MainWindow::reloadEndpointWidgets() { size_t i = 0; - for (; i < (osh->getEndpointHandlers().size()); i++) { - log_debugcpp("EPWidget creation"); - osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); - EndpointWidget *epw = new EndpointWidget(i, osh->getEndpointHandlers().at(i), widget); - //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET - ews.push_back(epw); - layout->addWidget(epw, i, 0); - + for (size_t epwIndex = 0; i < (osh->getEndpointHandlers().size()); i++) { + if (osh->getEndpointHandlers().at(i)->getState() == EndpointState::ENDPOINT_ACTIVE){ + log_debugcpp("EPWidget creation"); + osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); + EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getEndpointHandlers().at(i), widget); + epwIndex++; + //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET + ews.push_back(epw); + layout->addWidget(epw, i, 0); + } } - + //todo:: tas aqui tirao, no me gustas y probablemente yo a ti tampoco layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding), i, 0); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index db9917a..77265e8 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -61,6 +61,10 @@ class ExtendedCheckBox : public QCheckBox { Q_OBJECT public: + //c++11: this inherits all parent's constructors unconditionally + using QCheckBox::QCheckBox; + //alternative being calling parent ctor directly after declaring child ctor: + //B(int x) : A(x) { } bool event(QEvent* ev) override; }; From a15037c5f253f4e72997e88a99950810049273d6 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 7 Sep 2023 21:50:45 +0200 Subject: [PATCH 44/57] wip chex still buggy + customEvent --- src/back/backlasses.cpp | 32 +++++++++----------- src/cont/contclasses.cpp | 40 ++++++++++++++++++++++++- src/cont/contclasses.h | 19 ++++++++++-- src/global.h | 1 + src/qt/qtclasses.cpp | 64 ++++++++++++++++++++++++++++++++++++---- src/qt/qtclasses.h | 17 ++++++++++- 6 files changed, 145 insertions(+), 28 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index c726753..93122f0 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -141,19 +141,16 @@ HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { return S_OK; } -HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { +HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { + std::wstring endpointId = std::wstring(pwstrDeviceId); switch (dwNewState){ case DEVICE_STATE_ACTIVE: - + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_ACTIVE); break; case DEVICE_STATE_DISABLED: - - break; case DEVICE_STATE_NOTPRESENT: - - break; case DEVICE_STATE_UNPLUGGED: - + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_DISABLED); break; } @@ -161,16 +158,15 @@ HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, D } HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { - /* - * printf(" -->Changed device property " - * "{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}#%d\n", - * key.fmtid.Data1, key.fmtid.Data2, key.fmtid.Data3, - * key.fmtid.Data4[0], key.fmtid.Data4[1], - * key.fmtid.Data4[2], key.fmtid.Data4[3], - * key.fmtid.Data4[4], key.fmtid.Data4[5], - * key.fmtid.Data4[6], key.fmtid.Data4[7], - * key.pid); - */ + + log_debugcpp(" -->Changed device property " << + key.fmtid.Data1 << key.fmtid.Data2 << key.fmtid.Data3 << "\n" << + key.fmtid.Data4[0]<< key.fmtid.Data4[1]<< "\n"<< + key.fmtid.Data4[2]<< key.fmtid.Data4[3] << "\n"<< + key.fmtid.Data4[4]<< key.fmtid.Data4[5] << "\n"<< + key.fmtid.Data4[6]<< key.fmtid.Data4[7]<< "\n"<< + " pid " << key.pid); + return S_OK; } @@ -391,7 +387,7 @@ void Overseer::reloadEndpoints() { IMMDeviceCollection *deviceCollection; // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED // NOTPRESENT shows a lot of garbage, unnamed devices. - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection) )) + if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED, &deviceCollection) )) { log_debugcpp("si"); }; /* diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 28864c5..0d38d46 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -19,6 +19,19 @@ EndpointHandler::EndpointHandler(uint64_t idx) { } } +void EndpointHandler::setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx){ + ephfv.visibility = state; + ephfv.frontIdx = frontIdx; +} + +uint64_t EndpointHandler::getFrontVisibilityIndex(){ + return ephfv.frontIdx; +} + +EndpointState EndpointHandler::getFrontVisibilityState(){ + return ephfv.visibility; +} + /* these two, currently unused. If I use them, I should feel bad. * Endpoint* EndpointHandler::getEndpoint() { * return this->ep; @@ -152,7 +165,32 @@ void OverseerHandler::setChangeFrontDefaultsFunction(std::functionchangeFrontDefaults(role, endpointId); -} +} + +void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { + EndpointHandler* affected = nullptr; + for (auto eph : this->endpointHandlers) { + if (eph->getId() == endpointId) { + affected = eph; + break; + } + } + //todo: + if(EndpointState::ENDPOINT_ACTIVE & state) { + //todo: + return; + } else if (affected->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ + this->removeEndpointWidget(affected->getFrontVisibilityIndex()); + affected->setFrontVisibilityInfo(EndpointState::ENDPOINT_ALL, INT_MAX); + } + return; + //this->reviseEndpointShowing(endpointId, role); +} + +void OverseerHandler::setRemoveEndpointWidgetFunction(std::function removeEndpointWidget){ + this->removeEndpointWidget = removeEndpointWidget; +} + void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->endpointHandlers = ephs; diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 7ff6882..8bd07b0 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -76,7 +76,11 @@ public: std::wstring getName(); std::wstring getId(); - + + void setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx); + uint64_t getFrontVisibilityIndex(); + EndpointState getFrontVisibilityState(); + float getVolume(int channel); bool getMute(); size_t getState(); @@ -95,6 +99,11 @@ private: Endpoint *ep = nullptr; EndpointVolumeCallback *epc = nullptr; BackEndpointVolumeCallbackInfo callbackInfo; + struct EndpointHandlerFrontVisibility { + EndpointState visibility = EndpointState::ENDPOINT_ALL; + uint64_t frontIdx = INT_MAX; + }; + EndpointHandlerFrontVisibility ephfv; //QSlider *slidy; }; @@ -105,7 +114,10 @@ public: OverseerHandler(); void setChangeFrontDefaultsFunction(std::function changeFrontDefaults); void changeFrontDefaultsCallback(Roles role, std::wstring endpointId); - + + //void setReviseEndpointShowingFunction(std::function reviseEndpointShowing); + void reviseEndpointShowing(std::wstring endpointId, EndpointState state); + void setRemoveEndpointWidgetFunction(std::function removeEndpointWidget); void setEndpointHandlers(std::vector ephs); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); @@ -116,7 +128,8 @@ public: private: Overseer *os; std::vector endpointHandlers; - std::function changeFrontDefaults; + std::function changeFrontDefaults; + std::function removeEndpointWidget; //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/global.h b/src/global.h index 0ae2a75..0d7de5e 100644 --- a/src/global.h +++ b/src/global.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "debug.h" diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 38b0e44..6a49b52 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,9 +1,12 @@ #include "qtclasses.h" - +EndpointWidgetEvent::EndpointWidgetEvent(QEvent::Type type, int idx) : QEvent(type){ + this->idx = idx; +} bool ExtendedCheckBox::event(QEvent* ev) { if (ev->type() == QEvent::User) { + //todo: still prone to bugs; whack-a-mole to come this->blockSignals(true); if (this->isEnabled()) { this->setCheckState(Qt::Checked); @@ -19,11 +22,11 @@ bool ExtendedCheckBox::event(QEvent* ev) { return QCheckBox::event(ev); } - EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ //todo: based on qgridlayout, name+mute should be its own widget, same with channels this->idx = idx; this->eph = eph; + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); layout = new QGridLayout(this); //this->setLayout(layout); log_debugcpp("epw main layout parent: "<< layout->parent()); @@ -178,6 +181,31 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("ENDPOINT_WIDGETED"); } +void MainWindow::customEvent(QEvent* ev) { + if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetObsolete) { + this->removeEndpointWidget((EndpointWidgetEvent*)ev); + return; + } + // Make sure the rest of events are handled + return QMainWindow::customEvent(ev); +} + +void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ + uint64_t i = ev->idx; + this->ews.at(i)->setParent(nullptr); + this->layout->removeWidget(ews.at(i)); + uint64_t saisu = ews.size(); + //delete ews.at(index); + while ((i + 1) < ews.size()) { + ews.at(i) = ews.at(i + 1); + ews.at(i)->updateEndpointHandlerFrontInfo(i); + i++; + } + ews.pop_back(); + return; +} + + void EndpointWidget::updateMute(int checked){ bool muted = (checked == 2 ? true : false); this->eph->setMute(osh->getGuid(), muted); @@ -223,6 +251,11 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ return this->eph; } +void EndpointWidget::updateEndpointHandlerFrontInfo(uint64_t index){ + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, index); +} + + void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; } @@ -234,18 +267,24 @@ uint64_t EndpointWidget::getIndex(){ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // setWindowState(Qt::WindowFullScreen); // setCentralWidget(centralWidget); + + /* + * Registering needed custom events + */ + QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); + widget = new QWidget(); layout = new QGridLayout(); trayIcon = new QSystemTrayIcon(); trayIconMenu = new QMenu(); trayIconMenuQuit = new QAction(STRING_QUIT); + changeDefaultCheckboxEnablement = new QEvent(QEvent::User); changeDefaultCheckboxEnablement->setAccepted(true); widget->setLayout(layout); setCentralWidget(widget); //layout->addWidget(pintas, 0, 0); - setWindowTitle(STRING_TITLE); reloadEndpointWidgets(); @@ -266,7 +305,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { connect(trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivated); /* - * Set of function callback definitinos for EndpointSituationCallback + * Set of function callback definitons for EndpointSituationCallback */ osh->setChangeFrontDefaultsFunction([this](Roles role, std::wstring endpointId) { for (auto epw : this->ews) { @@ -306,8 +345,23 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { } } }); + + osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { + EndpointWidgetEvent removeObsoleteEndpointWidget((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index); + removeObsoleteEndpointWidget.setAccepted(true); + QCoreApplication::instance()->postEvent(this, &removeObsoleteEndpointWidget); + }); + + /* + * osh->setReviseEndpointShowingFunction([this](std::wstring endpointId, Roles role){ + * + * + * }); + */ + } + void MainWindow::closeEvent(QCloseEvent *event) { if (!event->spontaneous() || !isVisible()) return; @@ -338,7 +392,7 @@ void MainWindow::reloadEndpointWidgets() { osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getEndpointHandlers().at(i), widget); epwIndex++; - //TODO: ALWAYS PUSH BACK??? PSZ CHANGE DIS WHEN IMPLEMENTING DYN ENDPOINT DET + //alfinal estoes solopara inicializarlmao ews.push_back(epw); layout->addWidget(epw, i, 0); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 77265e8..b81dbed 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -56,7 +56,17 @@ * ToggleButton(QWidget *parent = nullptr); * }; */ +enum CustomQEvent { + EndpointWidgetObsolete = 1001, +}; +class EndpointWidgetEvent : public QEvent { + Q_OBJECT + +public: + EndpointWidgetEvent(QEvent::Type type, int idx); + uint64_t idx; +}; class ExtendedCheckBox : public QCheckBox { Q_OBJECT @@ -76,6 +86,8 @@ public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); EndpointHandler* getEndpointHandler(); + void updateEndpointHandlerFrontInfo(uint64_t index); + void setIndex(uint64_t idx); uint64_t getIndex(); @@ -124,9 +136,11 @@ public: protected: void closeEvent(QCloseEvent *event) override; - + void customEvent(QEvent* ev) override; + private slots: void trayIconActivated(QSystemTrayIcon::ActivationReason reason); + void removeEndpointWidget(EndpointWidgetEvent* ev); //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); @@ -140,6 +154,7 @@ private: QMenu *trayIconMenu; QAction *trayIconMenuQuit; QEvent* changeDefaultCheckboxEnablement; + //public slots: // void setEndpointHandlers(std::vector *ephs); From 6744a64fce37bb4e71b79b0aa89d02a130a5c335 Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 7 Sep 2023 22:51:55 +0200 Subject: [PATCH 45/57] wip remove epw now working, xekboxs still funky --- src/qt/qtclasses.cpp | 8 ++++---- src/qt/qtclasses.h | 4 ++-- src/qtestmain.cpp | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 6a49b52..2ff112c 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -182,7 +182,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } void MainWindow::customEvent(QEvent* ev) { - if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetObsolete) { + if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { this->removeEndpointWidget((EndpointWidgetEvent*)ev); return; } @@ -347,9 +347,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { }); osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { - EndpointWidgetEvent removeObsoleteEndpointWidget((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index); - removeObsoleteEndpointWidget.setAccepted(true); - QCoreApplication::instance()->postEvent(this, &removeObsoleteEndpointWidget); + EndpointWidgetEvent* removeObsoleteEndpointWidget = new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index); + removeObsoleteEndpointWidget->setAccepted(true); + QCoreApplication::instance()->postEvent(this, removeObsoleteEndpointWidget); }); /* diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index b81dbed..e2a3896 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -61,12 +61,12 @@ enum CustomQEvent { }; class EndpointWidgetEvent : public QEvent { - Q_OBJECT - + public: EndpointWidgetEvent(QEvent::Type type, int idx); uint64_t idx; }; +//Q_DECLARE_METATYPE(EndpointWidgetEvent) class ExtendedCheckBox : public QCheckBox { Q_OBJECT diff --git a/src/qtestmain.cpp b/src/qtestmain.cpp index cb32200..19cf627 100644 --- a/src/qtestmain.cpp +++ b/src/qtestmain.cpp @@ -45,7 +45,8 @@ int main (int argc, char* argv[]) { else exit(0); osh = new OverseerHandler(); - + //qRegisterMetaType(); + //INIT CONT log_debugcpp("main init"); osh->reloadEndpointHandlers(); From 60e3178e9ab62cde72468ada94517ff41e8c86ca Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 8 Sep 2023 18:48:51 +0200 Subject: [PATCH 46/57] fixed xekboxes --- qtest.pro | 6 +++--- src/back/backlasses.cpp | 40 ++++++++++++++++++++++------------------ src/back/backlasses.h | 11 ++++++----- src/cont/contclasses.cpp | 10 +++++----- src/debug.h | 19 ++++++++++++++++--- src/qt/qtclasses.cpp | 30 +++++++++++++++--------------- src/qt/qtclasses.h | 12 +++++++----- 7 files changed, 74 insertions(+), 54 deletions(-) diff --git a/qtest.pro b/qtest.pro index d867f27..dd5e215 100644 --- a/qtest.pro +++ b/qtest.pro @@ -2,14 +2,14 @@ QMAKE_CXXFLAGS += --target=x86_64-w64-mingw32 -g -gcodeview QMAKE_LFLAGS += --target=x86_64-w64-mingw32 -g -Wl,-pdb= -v LIBS += -LC:/capybara/libclang/x86_64-w64-mingw32/lib -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 #"kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" -luuid -loleaut32 -lole32 -lshell32 -ladvapi32 -lcomdlg32 -lwinspool -lgdi32 -luser32 -lkernel32 -DEFINES += DEBUG -CONFIG += debug console +DEFINES += DEBUG QT_LOGGING_TO_CONSOLE=1 WIN32_LEAN_AND_MEAN +CONFIG += debug QT += widgets network INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" -SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp +SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h ipolicyconfig.h RESOURCES = assets.qrc diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 93122f0..f76b8a1 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -112,7 +112,7 @@ HRESULT EndpointSituationCallback::QueryInterface(REFIID riid, VOID **ppvInterfa HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole role,LPCWSTR pwstrDeviceId) { if (flow == EDataFlow::eCapture) return E_INVALIDARG; - + Roles nRole; switch (role) { case ERole::eConsole: @@ -126,18 +126,19 @@ HRESULT EndpointSituationCallback::OnDefaultDeviceChanged(EDataFlow flow, ERole break; } std::wstring wstringEndpointId = pwstrDeviceId; + log_wdebugcpp(L"we got za defol 4 " + wstringEndpointId); osh->changeFrontDefaultsCallback(nRole, wstringEndpointId); return S_OK; } HRESULT EndpointSituationCallback::OnDeviceAdded(LPCWSTR pwstrDeviceId) { - log_wdebugcpp(L"ayo we eventing za adin" << pwstrDeviceId); + log_wdebugcpp(L"ayo we eventing za adin " + std::wstring(pwstrDeviceId)); return S_OK; }; HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { - log_wdebugcpp(L"ayo we eventing za rmovin" << pwstrDeviceId); + log_wdebugcpp(L"ayo we eventing za rmovin " + std::wstring(pwstrDeviceId)); return S_OK; } @@ -159,13 +160,15 @@ HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, D HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { - log_debugcpp(" -->Changed device property " << - key.fmtid.Data1 << key.fmtid.Data2 << key.fmtid.Data3 << "\n" << - key.fmtid.Data4[0]<< key.fmtid.Data4[1]<< "\n"<< - key.fmtid.Data4[2]<< key.fmtid.Data4[3] << "\n"<< - key.fmtid.Data4[4]<< key.fmtid.Data4[5] << "\n"<< - key.fmtid.Data4[6]<< key.fmtid.Data4[7]<< "\n"<< - " pid " << key.pid); + /* + * log_debugcpp(" -->Changed device property " + + * key.fmtid.Data1 + key.fmtid.Data2 + key.fmtid.Data3 + "\n" + + * key.fmtid.Data4[0]+ key.fmtid.Data4[1]+ "\n"+ + * key.fmtid.Data4[2]+ key.fmtid.Data4[3] + "\n"+ + * key.fmtid.Data4[4]+ key.fmtid.Data4[5] + "\n"+ + * key.fmtid.Data4[6]+ key.fmtid.Data4[7]+ "\n"+ + * " pid " + key.pid); + */ return S_OK; } @@ -272,7 +275,7 @@ void Endpoint::removeVolumeCallback(EndpointVolumeCallback *epc){ endpointVolume->UnregisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } -uint8_t Endpoint::getRoles(){ +Roles Endpoint::getRoles(){ return this->endpointRoles; } @@ -343,13 +346,13 @@ void Endpoint::setRoles(Roles role){ } } -void Endpoint::assignRoles(uint8_t role){ - uint8_t roles = endpointRoles | role; +void Endpoint::assignRoles(Roles role){ + Roles roles = (Roles)(endpointRoles | role); this->endpointRoles = roles; } -void Endpoint::removeRoles(uint8_t role){ - uint8_t roles = endpointRoles ^ role; +void Endpoint::removeRoles(Roles role){ + Roles roles = (Roles)(endpointRoles ^ role); this->endpointRoles = roles; } @@ -361,6 +364,7 @@ Endpoint::~Endpoint(){ } void Overseer::initCOMLibrary() { + OutputDebugStringW(L"EPWidget creation\n"); if(FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) { log_debugcpp("si"); }; @@ -405,7 +409,7 @@ void Overseer::reloadEndpoints() { Endpoint *endpoint = new Endpoint(temp, i); //endpoint->setIndex(i); this->playbackDevices.push_back(endpoint); - //TODO: le porblemx std::cout << "ola" << std::endl; + //TODO: le porblemx std::cout + "ola" + std::endl; } deviceCollection->Release(); @@ -435,8 +439,8 @@ void Overseer::reloadEndpoints() { temp->GetId(&id); int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); if (comparison - 2 == 0) { - log_wdebugcpp("ola defaul de " << i << " es " << id); - playbackDevices.at(j)->assignRoles((1 << i)); + log_wdebugcpp(L"ola defaul de " + std::to_wstring(i) + L" es " + id); + playbackDevices.at(j)->assignRoles((Roles)(1 << i)); } //uint8_t debg = playbackDevices.at(j)->getRoles(); } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 44407a0..97b52d4 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,5 +1,5 @@ #pragma once -#define WIN32_LEAN_AND_MEAN + #define _WIN32_WINNT 0x0A00 #include @@ -12,6 +12,7 @@ #include #include #include +//#include #include #include @@ -41,10 +42,10 @@ class Endpoint { bool getMute(); void setState(uint8_t state); size_t getState(); - uint8_t getRoles(); + Roles getRoles(); void setRoles(Roles role); - void assignRoles(uint8_t role); - void removeRoles(uint8_t role); + void assignRoles(Roles role); + void removeRoles(Roles role); std::wstring getId(); std::wstring getName(); void setVolumeCallback(EndpointVolumeCallback *epc); @@ -59,7 +60,7 @@ class Endpoint { std::wstring friendlyName; std::wstring endpointId; unsigned long endpointState; - uint8_t endpointRoles = 0; + Roles endpointRoles = (Roles)0; uint64_t idx; }; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 0d38d46..ce46d1e 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -104,11 +104,11 @@ void EndpointHandler::setRoles(Roles newRole){ } void EndpointHandler::assignRoles(Roles newRole){ - ep->assignRoles((uint8_t)newRole); + ep->assignRoles(newRole); } void EndpointHandler::removeRoles(Roles newRole){ - ep->removeRoles((uint8_t)newRole); + ep->removeRoles(newRole); } EndpointHandler::~EndpointHandler() { @@ -136,17 +136,17 @@ uint64_t OverseerHandler::getPlaybackEndpointsCount(){ void OverseerHandler::reloadEndpointHandlers(){ //std::vector* ephs = new std::vector; - log_debugcpp(" VSize: " << this->getPlaybackEndpointsCount()); + log_debugcpp(" VSize: " + std::to_string(this->getPlaybackEndpointsCount())); for(uint64_t i = 0; i < this->getPlaybackEndpointsCount(); i++){ - log_debugcpp("Creating handler " << i); + log_debugcpp("Creating handler " + std::to_string(i)); if(i < (this->endpointHandlers.size()) && this->endpointHandlers.at(i) != nullptr) delete endpointHandlers.at(i); EndpointHandler* eph = new EndpointHandler(i); - log_debugcpp("Created handler " << i << ", adding to vector. " << " VSize: " << this->getPlaybackEndpointsCount()); + log_debugcpp("Created handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->getPlaybackEndpointsCount())); if (i >= this->endpointHandlers.size()) endpointHandlers.push_back(eph); diff --git a/src/debug.h b/src/debug.h index 8e309cf..da6285c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -7,7 +7,7 @@ std::bitset varToBitset(T info) { std::bitset content(info); return content; } - +#ifndef _WIN32 #define log_debugcpp(str) do { \ std::cout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ } while (0) @@ -15,6 +15,21 @@ std::bitset varToBitset(T info) { #define log_wdebugcpp(str) do { \ std::wcout << "[DEBUG]" << "(" << __FILE__ << ":" << __LINE__ << "): " << str << std::endl; \ } while (0) +#else + +#include +#include +#define WIDE2(x) L##x +#define WIDE1(x) WIDE2(x) +#define WFILE WIDE1(__FILE__) +#define log_debugcpp(str) { \ + OutputDebugStringA(std::string("[DEBUG] (" + std::string(__FILE__) + ":" + std::to_string(__LINE__) + "): " + std::string(str) + "\n").c_str()); \ + } while (0) + +#define log_wdebugcpp(str) do { \ + OutputDebugStringW(std::wstring(L"[DEBUG] (" + std::wstring(WFILE) + L":" + std::to_wstring(__LINE__) + L"): " + std::wstring(str) +L"\n").c_str()); \ + } while (0) +#endif #define print_as_binary(len, type, info) varToBitset(info) @@ -30,5 +45,3 @@ std::bitset varToBitset(T info) { /* typedef void (EndpointWidget::*epwChannelVolumeFunc)(uint32_t channel, float newValue); */ /* typedef void (EndpointWidget::*epwToggleFrontFunc)(bool active); */ - - diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 2ff112c..0e47fc6 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -4,9 +4,11 @@ EndpointWidgetEvent::EndpointWidgetEvent(QEvent::Type type, int idx) : QEvent(ty this->idx = idx; } -bool ExtendedCheckBox::event(QEvent* ev) { - if (ev->type() == QEvent::User) { +void ExtendedCheckBox::customEvent(QEvent* ev) { + QEvent::Type tipo = ev->type(); + if (ev->type() == (QEvent::Type)CustomQEvent::EndpointDefaultChange) { //todo: still prone to bugs; whack-a-mole to come + ev->setAccepted(true); this->blockSignals(true); if (this->isEnabled()) { this->setCheckState(Qt::Checked); @@ -16,10 +18,10 @@ bool ExtendedCheckBox::event(QEvent* ev) { this->setCheckState(Qt::Unchecked); } this->blockSignals(false); - return true; + return; } // Make sure the rest of events are handled - return QCheckBox::event(ev); + QCheckBox::customEvent(ev); } EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ @@ -29,7 +31,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); layout = new QGridLayout(this); //this->setLayout(layout); - log_debugcpp("epw main layout parent: "<< layout->parent()); + log_debugcpp("epw main layout parent: " + std::to_string((intptr_t)(layout->parent()))); if (parent == nullptr) { log_debugcpp("owo?"); } defaultRolesCheckBoxes = { @@ -64,7 +66,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare muteButton->setText(eph->getMute() ? STRING_UNMUTE : STRING_MUTE); float volume = eph->getVolume(AudioChannel::CHANNEL_MAIN) * 100; mainSlider->setValue((int)volume); - log_debugcpp("ENDPOINT SET WITH VOLUME " << volume); + log_debugcpp("ENDPOINT SET WITH VOLUME " + std::to_string(volume)); //tip: would need to be new widget with layout in it //mainMuteLayout = new QGridLayout(); @@ -183,11 +185,12 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare void MainWindow::customEvent(QEvent* ev) { if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { + ev->setAccepted(true); this->removeEndpointWidget((EndpointWidgetEvent*)ev); return; } // Make sure the rest of events are handled - return QMainWindow::customEvent(ev); + QMainWindow::customEvent(ev); } void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ @@ -272,6 +275,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { * Registering needed custom events */ QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); + QEvent::registerEventType(CustomQEvent::EndpointDefaultChange); widget = new QWidget(); layout = new QGridLayout(); @@ -279,9 +283,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { trayIconMenu = new QMenu(); trayIconMenuQuit = new QAction(STRING_QUIT); - changeDefaultCheckboxEnablement = new QEvent(QEvent::User); - changeDefaultCheckboxEnablement->setAccepted(true); - widget->setLayout(layout); setCentralWidget(widget); //layout->addWidget(pintas, 0, 0); @@ -317,13 +318,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); epw->getEndpointHandler()->assignRoles(role); epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); //epw->defaultRolesCheckBoxes.at(role)->postEnableChange(); /* * And were you THE default? */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } /* * Are you the dethroned king? @@ -333,14 +334,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { * And were you THE default up until now? */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), changeDefaultCheckboxEnablement); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); //Same as before. ini-san will come... epw->getEndpointHandler()->removeRoles(role); epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), changeDefaultCheckboxEnablement); + QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } } @@ -348,7 +349,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { EndpointWidgetEvent* removeObsoleteEndpointWidget = new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index); - removeObsoleteEndpointWidget->setAccepted(true); QCoreApplication::instance()->postEvent(this, removeObsoleteEndpointWidget); }); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index e2a3896..f66b5b3 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -58,6 +58,7 @@ */ enum CustomQEvent { EndpointWidgetObsolete = 1001, + EndpointDefaultChange = 1002, }; class EndpointWidgetEvent : public QEvent { @@ -69,18 +70,20 @@ public: //Q_DECLARE_METATYPE(EndpointWidgetEvent) class ExtendedCheckBox : public QCheckBox { - Q_OBJECT + Q_OBJECT +protected: + void customEvent(QEvent* ev) override; + public: //c++11: this inherits all parent's constructors unconditionally using QCheckBox::QCheckBox; //alternative being calling parent ctor directly after declaring child ctor: //B(int x) : A(x) { } - bool event(QEvent* ev) override; }; class EndpointWidget : public QWidget { - Q_OBJECT +Q_OBJECT public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); @@ -153,8 +156,7 @@ private: QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; QAction *trayIconMenuQuit; - QEvent* changeDefaultCheckboxEnablement; - + //public slots: // void setEndpointHandlers(std::vector *ephs); From 14fae226bc72ed44f8c4dfe68f84f86cb5bb2aee Mon Sep 17 00:00:00 2001 From: Hane Date: Sun, 10 Sep 2023 21:58:01 +0200 Subject: [PATCH 47/57] wip: heap corruption (3rd item main volume) --- src/back/backlasses.cpp | 24 ++++++++++++++---- src/back/backlasses.h | 5 +++- src/cont/contclasses.cpp | 54 ++++++++++++++++++++++++++-------------- src/cont/contclasses.h | 8 +++++- src/qt/qtclasses.cpp | 53 +++++++++++++++++++++++---------------- src/qt/qtclasses.h | 15 +++++++---- 6 files changed, 107 insertions(+), 52 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index f76b8a1..d187a8d 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -184,11 +184,9 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-1);}; - if (this->endpointState == DEVICE_STATE_ACTIVE) { - if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&endpointVolume))) { /* log_debugcpp("si"); */ }; + activateEndpointVolume(); - if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {};/* log_debugcpp("get channel count fail"); */ - } + reloadEndpointChannels(); //todo:: atexit into exit Gather ID LPWSTR tempString = nullptr; @@ -206,6 +204,17 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ friendlyName = std::wstring(pv.pwszVal); } +void Endpoint::activateEndpointVolume() { + if (this->endpointVolume == nullptr) + if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume))) { log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); }; +} + +void Endpoint::reloadEndpointChannels() { + if (this->endpointState == DEVICE_STATE_ACTIVE) { + if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {};/* log_debugcpp("get channel count fail"); */ + } +} + void Endpoint::setIndex(uint64_t idx){ this->idx = idx; } @@ -246,6 +255,8 @@ bool Endpoint::getMute(){ void Endpoint::setState(uint8_t state){ this->endpointState = state; + if(state == EndpointState::ENDPOINT_ACTIVE) + this->reloadEndpointChannels(); } size_t Endpoint::getState(){ @@ -264,10 +275,13 @@ void Endpoint::setVolume(NGuid guid, int channel, float volume) { void Endpoint::setMute(NGuid guid, bool muted) { GUID tempMsGuid = NGuidToGUID(guid); - if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { /* TIP: Above */ }; + if(FAILED(endpointVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"EndpointVolume null?")); }; } void Endpoint::setVolumeCallback(EndpointVolumeCallback *epc){ + if(endpointVolume == nullptr) { + this->activateEndpointVolume(); + } endpointVolume->RegisterControlChangeNotify((IAudioEndpointVolumeCallback*)epc); } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 97b52d4..3bf02d3 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -33,6 +33,7 @@ class Endpoint { public: Endpoint(IMMDevice* endpoint, uint64_t idx); + void reloadEndpointChannels(); uint64_t getIndex(); void setIndex(uint64_t idx); void setVolume(NGuid guid, int channel, float volume); @@ -53,9 +54,11 @@ class Endpoint { ~Endpoint(); private: + void inline activateEndpointVolume(); + uint32_t channelCount = 0; IMMDevice* endpoint; - IAudioEndpointVolume *endpointVolume ; + IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; std::wstring friendlyName; std::wstring endpointId; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index ce46d1e..e80d4c1 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -4,19 +4,13 @@ EndpointHandler::EndpointHandler(uint64_t idx) { //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); + this->idx = idx; this->ep = osh->getPlaybackEndpoints().at(idx); epc = new EndpointVolumeCallback(ep); + this->callbackInfo.caller = osh->getGuid(); + //epName = ep->getName(); - if (this->ep->getState() == EndpointState::ENDPOINT_ACTIVE) { - callbackInfo.muted = this->getMute(); - callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); - callbackInfo.channels = this->getChannelCount(); - ep->setVolumeCallback(epc); - callbackInfo.channelVolumes.resize(this->callbackInfo.channels); - for(uint32_t i = 0; i < this->getChannelCount(); i++){ - callbackInfo.channelVolumes[i] = this->getVolume(i); - } - } + this->setBackEndpointVolumeCallbackInfoContent(this->getState()); } void EndpointHandler::setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx){ @@ -91,8 +85,28 @@ size_t EndpointHandler::getState(){ return ep->getState(); } +void EndpointHandler::setBackEndpointVolumeCallbackInfoContent(uint8_t state) { + if(state == EndpointState::ENDPOINT_ACTIVE) { + callbackInfo.muted = this->getMute(); + callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); + callbackInfo.channels = this->getChannelCount(); + ep->setVolumeCallback(epc); + callbackInfo.channelVolumes.resize(this->callbackInfo.channels); + for(uint32_t i = 0; i < this->getChannelCount(); i++){ + callbackInfo.channelVolumes.at(i) = this->getVolume(i); + } + } +} + void EndpointHandler::setState(uint8_t state){ ep->setState(state); + this->setBackEndpointVolumeCallbackInfoContent(state); +} + +void EndpointHandler::setState(uint8_t state, uint64_t index){ + ep->setState(state); + this->setFrontVisibilityInfo((EndpointState)state, index); + this->setBackEndpointVolumeCallbackInfoContent(state); } uint8_t EndpointHandler::getRoles(){ @@ -168,25 +182,27 @@ void OverseerHandler::changeFrontDefaultsCallback(Roles role, std::wstring endpo } void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { - EndpointHandler* affected = nullptr; - for (auto eph : this->endpointHandlers) { - if (eph->getId() == endpointId) { - affected = eph; + EndpointHandler* eph = nullptr; + for (auto loopEph : this->endpointHandlers) { + if (loopEph->getId() == endpointId) { + eph = loopEph; break; } } //todo: if(EndpointState::ENDPOINT_ACTIVE & state) { - //todo: - return; - } else if (affected->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ - this->removeEndpointWidget(affected->getFrontVisibilityIndex()); - affected->setFrontVisibilityInfo(EndpointState::ENDPOINT_ALL, INT_MAX); + this->addEndpointWidget(eph); + } else if (eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ + this->removeEndpointWidget(eph->getFrontVisibilityIndex()); } return; //this->reviseEndpointShowing(endpointId, role); } +void OverseerHandler::setAddEndpointWidgetFunction(std::function addEndpointWidget){ + this->addEndpointWidget = addEndpointWidget; +} + void OverseerHandler::setRemoveEndpointWidgetFunction(std::function removeEndpointWidget){ this->removeEndpointWidget = removeEndpointWidget; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 8bd07b0..c71e604 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -62,6 +62,8 @@ class EndpointHandler { public: EndpointHandler(uint64_t idx); + void setBackEndpointVolumeCallbackInfoContent(uint8_t state); + //these two, currently unused. If I use them, I should feel bad. //EndpointVolumeCallback* getEndpointVolumeCallback(); //Endpoint* getEndpoint(); @@ -92,12 +94,14 @@ 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); ~EndpointHandler(); private: uint64_t idx; Endpoint *ep = nullptr; EndpointVolumeCallback *epc = nullptr; + BackEndpointVolumeCallbackInfo callbackInfo; struct EndpointHandlerFrontVisibility { EndpointState visibility = EndpointState::ENDPOINT_ALL; @@ -107,7 +111,6 @@ private: //QSlider *slidy; }; - class OverseerHandler { public: @@ -118,6 +121,8 @@ public: //void setReviseEndpointShowingFunction(std::function reviseEndpointShowing); void reviseEndpointShowing(std::wstring endpointId, EndpointState state); void setRemoveEndpointWidgetFunction(std::function removeEndpointWidget); + void setAddEndpointWidgetFunction(std::function addEndpointWidget); + void setEndpointHandlers(std::vector ephs); std::vector getEndpointHandlers(); std::vector getPlaybackEndpoints(); @@ -130,6 +135,7 @@ private: std::vector endpointHandlers; std::function changeFrontDefaults; std::function removeEndpointWidget; + std::function addEndpointWidget; //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 0e47fc6..97330dc 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,7 +1,8 @@ #include "qtclasses.h" -EndpointWidgetEvent::EndpointWidgetEvent(QEvent::Type type, int idx) : QEvent(type){ - this->idx = idx; +template +EndpointWidgetEvent::EndpointWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ + this->payload = payload; } void ExtendedCheckBox::customEvent(QEvent* ev) { @@ -28,11 +29,13 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //todo: based on qgridlayout, name+mute should be its own widget, same with channels this->idx = idx; this->eph = eph; - this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); + //todo: sussy + this->eph->setState(EndpointState::ENDPOINT_ACTIVE, idx); + layout = new QGridLayout(this); //this->setLayout(layout); log_debugcpp("epw main layout parent: " + std::to_string((intptr_t)(layout->parent()))); - if (parent == nullptr) { log_debugcpp("owo?"); } + if (parent == nullptr) { log_debugcpp("ayooooo?"); } defaultRolesCheckBoxes = { {Roles::ROLE_ALL, new ExtendedCheckBox(this)}, @@ -183,31 +186,44 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("ENDPOINT_WIDGETED"); } +EndpointWidget::~EndpointWidget() { + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ALL, INT_MAX); +} + void MainWindow::customEvent(QEvent* ev) { if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { ev->setAccepted(true); - this->removeEndpointWidget((EndpointWidgetEvent*)ev); + this->removeEndpointWidget((EndpointWidgetEvent*)ev); return; - } + } else if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetCreated) { + ev->setAccepted(true); + this->addEndpointWidget((EndpointWidgetEvent*)ev); + } // Make sure the rest of events are handled QMainWindow::customEvent(ev); } -void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ - uint64_t i = ev->idx; +void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ + uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->layout->removeWidget(ews.at(i)); uint64_t saisu = ews.size(); //delete ews.at(index); while ((i + 1) < ews.size()) { ews.at(i) = ews.at(i + 1); - ews.at(i)->updateEndpointHandlerFrontInfo(i); + ews.at(i)->updateEndpointHandlerFrontIndex(i); i++; } ews.pop_back(); return; } +void MainWindow::addEndpointWidget(EndpointWidgetEvent* ev){ + EndpointWidget* epw = new EndpointWidget(this->ews.size(), ev->payload, widget); + this->layout->addWidget(epw); + ews.push_back(epw); + return; +} void EndpointWidget::updateMute(int checked){ bool muted = (checked == 2 ? true : false); @@ -254,7 +270,7 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ return this->eph; } -void EndpointWidget::updateEndpointHandlerFrontInfo(uint64_t index){ +void EndpointWidget::updateEndpointHandlerFrontIndex(uint64_t index){ this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, index); } @@ -275,6 +291,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { * Registering needed custom events */ QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); + QEvent::registerEventType(CustomQEvent::EndpointWidgetCreated); QEvent::registerEventType(CustomQEvent::EndpointDefaultChange); widget = new QWidget(); @@ -348,20 +365,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { }); osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { - EndpointWidgetEvent* removeObsoleteEndpointWidget = new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index); - QCoreApplication::instance()->postEvent(this, removeObsoleteEndpointWidget); + QCoreApplication::instance()->postEvent(this, new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index)); }); - /* - * osh->setReviseEndpointShowingFunction([this](std::wstring endpointId, Roles role){ - * - * - * }); - */ - + osh->setAddEndpointWidgetFunction([this](EndpointHandler* eph) { + QCoreApplication::instance()->postEvent(this, new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetCreated, eph)); + }); } - void MainWindow::closeEvent(QCloseEvent *event) { if (!event->spontaneous() || !isVisible()) return; @@ -389,7 +400,7 @@ void MainWindow::reloadEndpointWidgets() { for (size_t epwIndex = 0; i < (osh->getEndpointHandlers().size()); i++) { if (osh->getEndpointHandlers().at(i)->getState() == EndpointState::ENDPOINT_ACTIVE){ log_debugcpp("EPWidget creation"); - osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); + //osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getEndpointHandlers().at(i), widget); epwIndex++; //alfinal estoes solopara inicializarlmao diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index f66b5b3..10e9a98 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -58,14 +58,17 @@ */ enum CustomQEvent { EndpointWidgetObsolete = 1001, - EndpointDefaultChange = 1002, + EndpointWidgetCreated = 1002, + EndpointDefaultChange = 1003, }; +template class EndpointWidgetEvent : public QEvent { public: - EndpointWidgetEvent(QEvent::Type type, int idx); - uint64_t idx; + EndpointWidgetEvent(QEvent::Type type, T payload); + T payload; + }; //Q_DECLARE_METATYPE(EndpointWidgetEvent) @@ -89,7 +92,7 @@ public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); EndpointHandler* getEndpointHandler(); - void updateEndpointHandlerFrontInfo(uint64_t index); + void updateEndpointHandlerFrontIndex(uint64_t index); void setIndex(uint64_t idx); uint64_t getIndex(); @@ -104,6 +107,7 @@ public: QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; std::map defaultRolesCheckBoxes; + ~EndpointWidget(); //void updateMainVolume(float newValue); //void updateVolume(uint32_t channel, float newValue); @@ -143,7 +147,8 @@ protected: private slots: void trayIconActivated(QSystemTrayIcon::ActivationReason reason); - void removeEndpointWidget(EndpointWidgetEvent* ev); + void removeEndpointWidget(EndpointWidgetEvent* ev); + void addEndpointWidget(EndpointWidgetEvent* ev); //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); From 6d2f981d35b34116882d51ac91db75157634e179 Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 11 Sep 2023 20:37:33 +0200 Subject: [PATCH 48/57] fixed stack corruption after refactor? --- src/back/backlasses.cpp | 18 ++++++++++-------- src/cont/contclasses.cpp | 4 +--- src/cont/contclasses.h | 1 + src/qt/qtclasses.cpp | 7 +++++-- src/qt/qtclasses.h | 2 +- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index d187a8d..4e4f4a1 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -54,15 +54,17 @@ HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify //memcpy(&osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; - UINT i = 0; - do { - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[i] = pNotify->afChannelVolumes[i]; - } while(i++ < pNotify->nChannels); - + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; + + UINT j = 0; + //todo: do while here caused stack corruption; sus + while(j < pNotify->nChannels) { + osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + j++; + } return S_OK; } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index e80d4c1..84411f7 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -139,7 +139,6 @@ std::vector OverseerHandler::getPlaybackEndpoints() { return this->os->getPlaybackEndpoints(); } - std::vector OverseerHandler::getEndpointHandlers(){ return endpointHandlers; } @@ -189,14 +188,13 @@ void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointSta break; } } - //todo: + if(EndpointState::ENDPOINT_ACTIVE & state) { this->addEndpointWidget(eph); } else if (eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ this->removeEndpointWidget(eph->getFrontVisibilityIndex()); } return; - //this->reviseEndpointShowing(endpointId, role); } void OverseerHandler::setAddEndpointWidgetFunction(std::function addEndpointWidget){ diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index c71e604..f071433 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -35,6 +35,7 @@ enum Roles { }; struct NGuid { + //todo: still leaking? uint32_t data1; uint16_t data2; uint16_t data3; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 97330dc..9da1154 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -187,6 +187,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } EndpointWidget::~EndpointWidget() { + timer->stop(); + delete timer; this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ALL, INT_MAX); } @@ -211,7 +213,7 @@ void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ //delete ews.at(index); while ((i + 1) < ews.size()) { ews.at(i) = ews.at(i + 1); - ews.at(i)->updateEndpointHandlerFrontIndex(i); + ews.at(i)->updateFrontIndex(i); i++; } ews.pop_back(); @@ -270,7 +272,8 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ return this->eph; } -void EndpointWidget::updateEndpointHandlerFrontIndex(uint64_t index){ +void EndpointWidget::updateFrontIndex(uint64_t index){ + this->idx = index; this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, index); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 10e9a98..9345a30 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -92,7 +92,7 @@ public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); EndpointHandler* getEndpointHandler(); - void updateEndpointHandlerFrontIndex(uint64_t index); + void updateFrontIndex(uint64_t index); void setIndex(uint64_t idx); uint64_t getIndex(); From f92df429951fd5a6da38b7df023f38d83720937f Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 12 Sep 2023 17:51:28 +0200 Subject: [PATCH 49/57] uuh... peak? --- src/qt/qtclasses.cpp | 35 +++++++++++++++++++++-------------- src/qt/qtclasses.h | 25 +++++++++++++------------ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 9da1154..8759873 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -172,6 +172,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); //TODO: el default = objcopy frees? + //Todo: like fr pregunta eph->getCallbackInfo()->caller = osh->getGuid(); mainSlider->blockSignals(false); muteButton->blockSignals(false); @@ -213,7 +214,7 @@ void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ //delete ews.at(index); while ((i + 1) < ews.size()) { ews.at(i) = ews.at(i + 1); - ews.at(i)->updateFrontIndex(i); + ews.at(i)->setIndex(i); i++; } ews.pop_back(); @@ -272,20 +273,26 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ return this->eph; } -void EndpointWidget::updateFrontIndex(uint64_t index){ - this->idx = index; - this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, index); -} - +/* + * void EndpointWidget::updateFrontIndex(uint64_t index){ + * this->idx = index; + * } + */ void EndpointWidget::setIndex(uint64_t idx){ this->idx = idx; + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, this->idx); } uint64_t EndpointWidget::getIndex(){ return idx; } +std::map EndpointWidget::getDefaultRolesWidgets() { + return defaultRolesCheckBoxes; +} + + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // setWindowState(Qt::WindowFullScreen); // setCentralWidget(centralWidget); @@ -335,16 +342,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { */ if (epw->getEndpointHandler()->getId() == endpointId) { //not necessary to keep endpointState flags up to date right now, but updating it will allow for later config files / profiles - epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); + epw->getDefaultRolesWidgets().at(role)->blockSignals(true); epw->getEndpointHandler()->assignRoles(role); - epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + epw->getDefaultRolesWidgets().at(role)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); //epw->defaultRolesCheckBoxes.at(role)->postEnableChange(); /* * And were you THE default? */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } /* * Are you the dethroned king? @@ -354,14 +361,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { * And were you THE default up until now? */ if (epw->getEndpointHandler()->getRoles() == Roles::ROLE_ALL) { - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(Roles::ROLE_ALL), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } - epw->defaultRolesCheckBoxes.at(role)->blockSignals(true); + epw->getDefaultRolesWidgets().at(role)->blockSignals(true); //Same as before. ini-san will come... epw->getEndpointHandler()->removeRoles(role); - epw->defaultRolesCheckBoxes.at(role)->blockSignals(false); - QCoreApplication::instance()->postEvent(epw->defaultRolesCheckBoxes.at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); + epw->getDefaultRolesWidgets().at(role)->blockSignals(false); + QCoreApplication::instance()->postEvent(epw->getDefaultRolesWidgets().at(role), new QEvent((QEvent::Type)CustomQEvent::EndpointDefaultChange)); } } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 9345a30..9a40d39 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -92,21 +92,12 @@ public: EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent = nullptr); EndpointHandler* getEndpointHandler(); - void updateFrontIndex(uint64_t index); - + std::map getDefaultRolesWidgets(); + void setIndex(uint64_t idx); uint64_t getIndex(); - void setVolume(int channel, float volume); - - QCheckBox *muteButton = nullptr; - QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; - QSlider *mainSlider = nullptr; - std::vector channelSliders; - std::vector channelLabels; - QGridLayout *layout = nullptr; - QGridLayout *mainMuteLayout = nullptr; - std::map defaultRolesCheckBoxes; + ~EndpointWidget(); //void updateMainVolume(float newValue); @@ -121,6 +112,16 @@ public slots: void updateMute(int checked); private: + QCheckBox *muteButton = nullptr; + QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; + QSlider *mainSlider = nullptr; + std::vector channelSliders; + std::vector channelLabels; + QGridLayout *layout = nullptr; + QGridLayout *mainMuteLayout = nullptr; + std::map defaultRolesCheckBoxes; + + EndpointHandler* eph; size_t defaultRolesVectorSize = 4; QTimer* timer = nullptr; From 44ccde6ac890d727a1c4e581a10d644f01c5c74f Mon Sep 17 00:00:00 2001 From: Hane Date: Thu, 14 Sep 2023 18:01:32 +0200 Subject: [PATCH 50/57] almost captured, only to hotplug. such is life... --- src/back/backfuncs.h | 1 - src/back/backlasses.cpp | 154 ++++++++++++++++++++++++++++++--------- src/back/backlasses.h | 21 +++++- src/cont/contclasses.cpp | 98 ++++++++++++++++++++----- src/cont/contclasses.h | 15 +++- src/qt/qtclasses.cpp | 11 +-- 6 files changed, 236 insertions(+), 64 deletions(-) diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h index f66a295..1062ba1 100644 --- a/src/back/backfuncs.h +++ b/src/back/backfuncs.h @@ -3,7 +3,6 @@ GUID NGuidToGUID(NGuid guid) { msGuid.Data1 = guid.data1; msGuid.Data2 = guid.data2; msGuid.Data3 = guid.data3; - msGuid.Data1 = guid.data1; for (int i = 0; i < 8; i++){ msGuid.Data4[i] = guid.data4[i]; //log_debugcpp("MSGUID DATA4 BYTE " << i << ": "); diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 4e4f4a1..8983942 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -39,30 +39,30 @@ HRESULT EndpointVolumeCallback::QueryInterface(REFIID riid, VOID **ppvInterface) HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) { if (pNotify == NULL) return E_INVALIDARG; - //delete osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller; - //osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.freeData4(); + //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->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data1 \ = pNotify->guidEventContext.Data1; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data2 \ = pNotify->guidEventContext.Data2; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data3 \ = pNotify->guidEventContext.Data3; for(int i = 0; i < 8 /* Data4 size */; i++){ - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = pNotify->guidEventContext.Data4[i]; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller.data4[i] = pNotify->guidEventContext.Data4[i]; } - //memcpy(&osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); + //memcpy(&osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; UINT j = 0; //todo: do while here caused stack corruption; sus while(j < pNotify->nChannels) { - osh->getEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; j++; } return S_OK; @@ -76,9 +76,10 @@ HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify * */ //todo: not on construct since it expects them to already exist; smells like refactor! -void EndpointSituationCallback::fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices){ +void EndpointSituationCallback::fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices, std::vector captureDevices){ this->deviceEnumerator = deviceEnumerator; this->playbackDevices = playbackDevices; + this->captureDevices = captureDevices; } ULONG EndpointSituationCallback::AddRef(){ @@ -151,10 +152,14 @@ HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, D osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_ACTIVE); break; case DEVICE_STATE_DISABLED: - case DEVICE_STATE_NOTPRESENT: - case DEVICE_STATE_UNPLUGGED: osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_DISABLED); break; + case DEVICE_STATE_NOTPRESENT: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_NOTPRESENT); + break; + case DEVICE_STATE_UNPLUGGED: + osh->reviseEndpointShowing(endpointId, EndpointState::ENDPOINT_UNPLUGGED); + break; } return S_OK; @@ -189,6 +194,11 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ activateEndpointVolume(); reloadEndpointChannels(); + + /* + * if(FAILED(endpoint->Activate(__uuidof(IAudioMeterInformation), + * CLSCTX_ALL, NULL, (void**)&endpointPeakMeter))) { log_debugcpp("peakbros..."); } + */ //todo:: atexit into exit Gather ID LPWSTR tempString = nullptr; @@ -206,6 +216,11 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ friendlyName = std::wstring(pv.pwszVal); } +/* + * Endpoint::Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; + */ + + void Endpoint::activateEndpointVolume() { if (this->endpointVolume == nullptr) if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume))) { log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); }; @@ -372,6 +387,33 @@ void Endpoint::removeRoles(Roles role){ this->endpointRoles = roles; } +void Endpoint::setFlow() { + IMMEndpoint* flowGetter; + //this should be as simple as writing IID_IMMEndpoint, but it just won't find the macro, so I copied it. Sad. + GUID manual; + manual.Data1 = 0x1be09788; + manual.Data2 = 0x6894; + manual.Data3 = 0x4089; + manual.Data4[0] = 0x85; + manual.Data4[1] = 0x86; + manual.Data4[2] = 0x9a; + manual.Data4[3] = 0x2a; + manual.Data4[4] = 0x6c; + manual.Data4[5] = 0x26; + manual.Data4[6] = 0x5a; + manual.Data4[7] = 0xc5; + if(FAILED(this->endpoint->QueryInterface((const _GUID)manual, (void**)&flowGetter))) + { log_debugcpp("no flow..."); } + EDataFlow MSflow; + HRESULT vafllar = flowGetter->GetDataFlow(&MSflow); + this->flow = (MSflow == EDataFlow::eRender ? Flows::FLOW_PLAYBACK : Flows::FLOW_CAPTURE); + flowGetter->Release(); +} + +Flows Endpoint::getFlow() { + return this->flow; +} + Endpoint::~Endpoint(){ log_debugcpp("murio endpoint-san uwu"); properties->Release(); @@ -394,6 +436,7 @@ void Overseer::initCOMLibrary() { 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), @@ -403,28 +446,34 @@ void Overseer::initCOMLibrary() { //TODO: Uninitialize COM } -void Overseer::reloadEndpoints() { +void Overseer::reloadEndpoints(Flows flow) { IMMDeviceCollection *deviceCollection; + unsigned int numEndpoints; + EDataFlow MSflow = (flow == Flows::FLOW_PLAYBACK ? EDataFlow::eRender : EDataFlow::eCapture); // | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED // NOTPRESENT shows a lot of garbage, unnamed devices. - if(FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED, &deviceCollection) )) + if(FAILED(deviceEnumerator->EnumAudioEndpoints(MSflow, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_NOTPRESENT | DEVICE_STATE_UNPLUGGED, &deviceCollection) )) { log_debugcpp("si"); }; /* * Counting them */ - if(FAILED(deviceCollection->GetCount(&numPlaybackEndpoints))) { log_debugcpp("si");}; - if(numPlaybackEndpoints == 0) { log_debugcpp("si"); }; + if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; + if(numEndpoints == 0) { log_debugcpp("si"); }; /* * Retrieving actual endpoints and storing them on their own class */ IMMDevice *temp; - for (unsigned int i = 0; i < numPlaybackEndpoints; i++){ + for (unsigned int i = 0; i < numEndpoints; i++){ if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; Endpoint *endpoint = new Endpoint(temp, i); + endpoint->setFlow(); //endpoint->setIndex(i); - this->playbackDevices.push_back(endpoint); + if (flow == Flows::FLOW_PLAYBACK) + this->playbackDevices.push_back(endpoint); + else + this->captureDevices.push_back(endpoint); //TODO: le porblemx std::cout + "ola" + std::endl; } @@ -447,30 +496,65 @@ void Overseer::reloadEndpoints() { val = eCommunications; break; } - deviceEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, val, &temp); + deviceEnumerator->GetDefaultAudioEndpoint(MSflow, val, &temp); LPWSTR id = nullptr; - - for (unsigned int j = 0; j < numPlaybackEndpoints; j++){ - std::wstring eptId = playbackDevices.at(j)->getId(); - temp->GetId(&id); - int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); - if (comparison - 2 == 0) { - log_wdebugcpp(L"ola defaul de " + std::to_wstring(i) + L" es " + id); - playbackDevices.at(j)->assignRoles((Roles)(1 << i)); + + if (flow == Flows::FLOW_PLAYBACK) { + for (unsigned int j = 0; j < numEndpoints; j++) { + std::wstring eptId = playbackDevices.at(j)->getId(); + temp->GetId(&id); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); + if (comparison - 2 == 0) { + log_wdebugcpp(L"ola defaul playback de " + + std::to_wstring(i) + L" es " + id); + playbackDevices.at(j)->assignRoles((Roles)(1 << i)); + } } - //uint8_t debg = playbackDevices.at(j)->getRoles(); - } + } else { + for (unsigned int j = 0; j < numEndpoints; j++){ + std::wstring eptId = captureDevices.at(j)->getId(); + temp->GetId(&id); + int comparison = CompareStringEx(LOCALE_NAME_USER_DEFAULT, 0, eptId.c_str(), -987, id, -987, NULL, NULL, 0); + if (comparison - 2 == 0) { + log_wdebugcpp(L"ola defaul capture de " + + std::to_wstring(i) + L" es " + id); + captureDevices.at(j)->assignRoles((Roles)(1 << i)); + } + } + } } } +Endpoint* Overseer::addEndpoint(std::wstring endpointId, /* out */Flows* flow = nullptr) { + IMMDevice* newep; + if(FAILED(deviceEnumerator->GetDevice((LPCWSTR)endpointId.c_str(), &newep))) + log_debugcpp("ay caramba con la hot metida."); + + Endpoint *endpoint = new Endpoint(newep); + + Flows getFlow = endpoint->getFlow(); + if (getFlow == Flows::FLOW_PLAYBACK) { + endpoint->setIndex(osh->getPlaybackEndpointsCount()); + this->playbackDevices.push_back(endpoint); + } else { + endpoint->setIndex(osh->getCaptureEndpointsCount()); + this->captureDevices.push_back(endpoint); + } + if (flow != nullptr) *flow = getFlow; + return endpoint; +} + Overseer::Overseer() { //: epsc(deviceEnumerator, playbackDevices){ //Initializing COM library log_debugcpp("Initializing Overseer"); initCOMLibrary(); //Obtaining playback endpoint collection on this point in time - reloadEndpoints(); - this->epsc.fill(deviceEnumerator, playbackDevices); + reloadEndpoints(Flows::FLOW_PLAYBACK); + //reloadEndpoints(Flows::FLOW_CAPTURE); + + //Registering for endpoint information callback + this->epsc.fill(deviceEnumerator, playbackDevices, captureDevices); if(FAILED(deviceEnumerator->RegisterEndpointNotificationCallback(((IMMNotificationClient*)&epsc)))) { log_debugcpp("when no enchufas......"); } } @@ -482,6 +566,10 @@ std::vector Overseer::getPlaybackEndpoints() { return playbackDevices; } +std::vector Overseer::getCaptureEndpoints() { + return captureDevices; +} + Overseer::~Overseer(){ log_debugcpp("cum"); deviceEnumerator->Release(); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 3bf02d3..ebfc07e 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -33,6 +33,8 @@ class Endpoint { public: Endpoint(IMMDevice* endpoint, uint64_t idx); + //todo: how to forward declare delegate constructors? + Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; void reloadEndpointChannels(); uint64_t getIndex(); void setIndex(uint64_t idx); @@ -47,6 +49,8 @@ class Endpoint { void setRoles(Roles role); void assignRoles(Roles role); void removeRoles(Roles role); + void setFlow(); + Flows getFlow(); std::wstring getId(); std::wstring getName(); void setVolumeCallback(EndpointVolumeCallback *epc); @@ -58,13 +62,17 @@ class Endpoint { uint32_t channelCount = 0; IMMDevice* endpoint; - IAudioEndpointVolume *endpointVolume = nullptr; + Flows flow; + IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; std::wstring friendlyName; std::wstring endpointId; unsigned long endpointState; Roles endpointRoles = (Roles)0; uint64_t idx; + /* Not implemented in llvm-mingw. Sad! + * IAudioMeterInformation *endpointPeakMeter = nullptr; + */ }; class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { @@ -95,11 +103,12 @@ class EndpointSituationCallback : public IMMNotificationClient { HRESULT OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState); HRESULT OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key); - void fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); + void fill(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices, std::vector captureDevices); private: ULONG ref = 1; IMMDeviceEnumerator *deviceEnumerator; std::vector playbackDevices; + std::vector captureDevices; }; class Overseer { @@ -107,7 +116,10 @@ class Overseer { public: Overseer(); std::vector getPlaybackEndpoints(); - void reloadEndpoints(); + std::vector getCaptureEndpoints(); + + void reloadEndpoints(Flows flow); + Endpoint* addEndpoint(std::wstring endpointId, /* out */ Flows* flow); NGuid getGuid(); //void setEndpointStatusCallback(); //void setEndpointStatusCallback(); @@ -121,11 +133,12 @@ class Overseer { private: NGuid guid; - unsigned int numPlaybackEndpoints; + IMMDeviceEnumerator *deviceEnumerator; EndpointSituationCallback epsc; //IPolicyConfig *policyConfig; std::vector playbackDevices; + std::vector captureDevices; void initCOMLibrary(); //IMMDeviceCollection *deviceCollection; //int numCaptureEndpoints; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 84411f7..937ee67 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -2,15 +2,28 @@ #include "contclasses.h" //TODO: pragma once -EndpointHandler::EndpointHandler(uint64_t idx) { +EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { //std::vector endpoints = osh->getPlaybackEndpoints().at(idx); this->idx = idx; - this->ep = osh->getPlaybackEndpoints().at(idx); + this->flow = flow; + this->ep = (flow == Flows::FLOW_PLAYBACK ? osh->getPlaybackEndpoints().at(idx) : osh->getCaptureEndpoints().at(idx)); + epc = new EndpointVolumeCallback(ep); this->callbackInfo.caller = osh->getGuid(); //epName = ep->getName(); - this->setBackEndpointVolumeCallbackInfoContent(this->getState()); + this->setBackEndpointVolumeCallbackInfoContent(this->getState()); + osh->pushBackEndpointHandler(this, flow); + +} + +void OverseerHandler::pushBackEndpointHandler(EndpointHandler* eph, Flows flow) { + if (eph == nullptr) return; + if (flow = Flows::FLOW_PLAYBACK) + playbackEndpointHandlers.push_back(eph); + else + captureEndpointHandlers.push_back(eph); + return; } void EndpointHandler::setFrontVisibilityInfo(EndpointState state, uint64_t frontIdx){ @@ -139,35 +152,79 @@ std::vector OverseerHandler::getPlaybackEndpoints() { return this->os->getPlaybackEndpoints(); } -std::vector OverseerHandler::getEndpointHandlers(){ - return endpointHandlers; +std::vector OverseerHandler::getCaptureEndpoints() { + return this->os->getCaptureEndpoints(); +} + +std::vector OverseerHandler::getPlaybackEndpointHandlers(){ + return playbackEndpointHandlers; +} + +std::vector OverseerHandler::getCaptureEndpointHandlers(){ + return captureEndpointHandlers; } uint64_t OverseerHandler::getPlaybackEndpointsCount(){ return this->os->getPlaybackEndpoints().size(); } +uint64_t OverseerHandler::getCaptureEndpointsCount(){ + return this->os->getCaptureEndpoints().size(); +} + void OverseerHandler::reloadEndpointHandlers(){ + //todo: add capture + //std::vector* ephs = new std::vector; - log_debugcpp(" VSize: " + std::to_string(this->getPlaybackEndpointsCount())); + log_debugcpp("Playback VSize: " + std::to_string(this->getPlaybackEndpointsCount())); for(uint64_t i = 0; i < this->getPlaybackEndpointsCount(); i++){ - log_debugcpp("Creating handler " + std::to_string(i)); + log_debugcpp("Creating Playback handler " + std::to_string(i)); + + EndpointHandler* ephexx = new EndpointHandler(i, Flows::FLOW_PLAYBACK); + //this->playbackEndpointHandlers.push_back(ephexx); + log_debugcpp("Created Playback handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->playbackEndpointHandlers.size())); - if(i < (this->endpointHandlers.size()) && - this->endpointHandlers.at(i) != nullptr) - delete endpointHandlers.at(i); + } + + log_debugcpp("Capture VSize: " + + std::to_string(this->getCaptureEndpointsCount())); + + for(uint64_t i = 0; i < this->getCaptureEndpointsCount(); i++){ + log_debugcpp("Creating Capture handler " + std::to_string(i)); - EndpointHandler* eph = new EndpointHandler(i); - log_debugcpp("Created handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->getPlaybackEndpointsCount())); + /* + * if(i < (this->captureEndpointHandlers.size()) && + * this->captureEndpointHandlers.at(i) != nullptr) + * delete captureEndpointHandlers.at(i); + */ - if (i >= this->endpointHandlers.size()) - endpointHandlers.push_back(eph); - else endpointHandlers.at(i) = eph; + EndpointHandler* ephoo = new EndpointHandler(i, Flows::FLOW_CAPTURE); + //this->captureEndpointHandlers.push_back(ephoo); + log_debugcpp("Created Capture handler " + std::to_string(i) + ", adding to vector. " + " VSize: " + std::to_string(this->captureEndpointHandlers.size())); + + /* + * if (i >= this->captureEndpointHandlers.size()) + * captureEndpointHandlers.push_back(eph); + * else captureEndpointHandlers.at(i) = eph; + */ } //setEndpointHandlers(ephs); } +EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId){ + Flows flow; + Endpoint* newEp = this->os->addEndpoint(endpointId, &flow); + + uint64_t ephIdx = (flow == Flows::FLOW_PLAYBACK ? this->getPlaybackEndpointsCount() : this->getCaptureEndpointsCount()); + + EndpointHandler* newEph = new EndpointHandler(ephIdx, flow); + // std::vector getPlaybackEndpointHandlers(); + //std::vector getCaptureEndpointHandlers(); + + return newEph; +} + NGuid OverseerHandler::getGuid() { return this->os->getGuid(); } @@ -182,13 +239,20 @@ void OverseerHandler::changeFrontDefaultsCallback(Roles role, std::wstring endpo void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { EndpointHandler* eph = nullptr; - for (auto loopEph : this->endpointHandlers) { + for (auto loopEph : this->playbackEndpointHandlers) { if (loopEph->getId() == endpointId) { eph = loopEph; break; } } + if (!eph) { + + } + + //todo: testing missing shiez. sowwy + //if(!eph) osh-> + if(EndpointState::ENDPOINT_ACTIVE & state) { this->addEndpointWidget(eph); } else if (eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ @@ -207,5 +271,5 @@ void OverseerHandler::setRemoveEndpointWidgetFunction(std::function ephs){ - this->endpointHandlers = ephs; + this->playbackEndpointHandlers = ephs; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index f071433..b64389d 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -62,7 +62,7 @@ struct BackEndpointVolumeCallbackInfo { class EndpointHandler { public: - EndpointHandler(uint64_t idx); + EndpointHandler(uint64_t idx, Flows flow); void setBackEndpointVolumeCallbackInfoContent(uint8_t state); //these two, currently unused. If I use them, I should feel bad. @@ -84,6 +84,7 @@ public: uint64_t getFrontVisibilityIndex(); EndpointState getFrontVisibilityState(); + Flows getFlow(); float getVolume(int channel); bool getMute(); size_t getState(); @@ -102,7 +103,7 @@ private: uint64_t idx; Endpoint *ep = nullptr; EndpointVolumeCallback *epc = nullptr; - + Flows flow; BackEndpointVolumeCallbackInfo callbackInfo; struct EndpointHandlerFrontVisibility { EndpointState visibility = EndpointState::ENDPOINT_ALL; @@ -125,15 +126,21 @@ public: void setAddEndpointWidgetFunction(std::function addEndpointWidget); void setEndpointHandlers(std::vector ephs); - std::vector getEndpointHandlers(); + std::vector getPlaybackEndpointHandlers(); + std::vector getCaptureEndpointHandlers(); std::vector getPlaybackEndpoints(); + std::vector getCaptureEndpoints(); + void pushBackEndpointHandler(EndpointHandler* eph, Flows flow); uint64_t getPlaybackEndpointsCount(); + uint64_t getCaptureEndpointsCount(); void reloadEndpointHandlers(); + EndpointHandler* addEndpoint(std::wstring endpointId); NGuid getGuid(); private: Overseer *os; - std::vector endpointHandlers; + std::vector playbackEndpointHandlers; + std::vector captureEndpointHandlers; std::function changeFrontDefaults; std::function removeEndpointWidget; std::function addEndpointWidget; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 8759873..da01a60 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -207,11 +207,12 @@ void MainWindow::customEvent(QEvent* ev) { } void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ - uint64_t i = ev->payload; + uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->layout->removeWidget(ews.at(i)); uint64_t saisu = ews.size(); //delete ews.at(index); + delete ews.at(i); while ((i + 1) < ews.size()) { ews.at(i) = ews.at(i + 1); ews.at(i)->setIndex(i); @@ -407,11 +408,11 @@ void MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason) { void MainWindow::reloadEndpointWidgets() { size_t i = 0; - for (size_t epwIndex = 0; i < (osh->getEndpointHandlers().size()); i++) { - if (osh->getEndpointHandlers().at(i)->getState() == EndpointState::ENDPOINT_ACTIVE){ + for (size_t epwIndex = 0; i < (osh->getPlaybackEndpointHandlers().size()); i++) { + if (osh->getPlaybackEndpointHandlers().at(i)->getState() == EndpointState::ENDPOINT_ACTIVE){ log_debugcpp("EPWidget creation"); - //osh->getEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); - EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getEndpointHandlers().at(i), widget); + //osh->getPlaybackEndpointHandlers().at(i)->getCallbackInfo()->caller = osh->getGuid(); + EndpointWidget *epw = new EndpointWidget(epwIndex, osh->getPlaybackEndpointHandlers().at(i), widget); epwIndex++; //alfinal estoes solopara inicializarlmao ews.push_back(epw); From 6f8455c63de3d6e38574600aef4cd2e95e599eb5 Mon Sep 17 00:00:00 2001 From: Hane Date: Fri, 15 Sep 2023 19:29:41 +0200 Subject: [PATCH 51/57] mic back/cont & plug/unplug done; devicead/rem to go --- src/back/backlasses.cpp | 64 +++++++++++++++++++++++++++++----------- src/cont/contclasses.cpp | 47 ++++++++++++++++++----------- src/cont/contclasses.h | 2 +- src/qt/qtclasses.cpp | 58 +++++++++++++++++++++++++++++------- src/qt/qtclasses.h | 4 ++- 5 files changed, 129 insertions(+), 46 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 8983942..66a8015 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -53,18 +53,28 @@ HRESULT EndpointVolumeCallback::OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify } //memcpy(&osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->caller, &pNotify->guidEventContext,sizeof(NGuid) ); - - - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->muted = pNotify->bMuted; - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->mainVolume = pNotify->fMasterVolume; - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channels = pNotify->nChannels; + 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) { - osh->getPlaybackEndpointHandlers().at(this->ep->getIndex())->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; - j++; - } + UINT j = 0; + //todo: do while here caused stack corruption; sus + while(j < pNotify->nChannels) { + if (flow & Flows::FLOW_PLAYBACK) + eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + else + eph->getCallbackInfo()->channelVolumes[j] = pNotify->afChannelVolumes[j]; + j++; + } return S_OK; } @@ -191,7 +201,9 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ //todo: preguntitas owindows dword no es uint32_t even tho mingw mingas if(FAILED(endpoint->GetState(&this->endpointState))) {exit(-1);}; - activateEndpointVolume(); + if(this->endpointState == EndpointState::ENDPOINT_ACTIVE) { + activateEndpointVolume(); + } reloadEndpointChannels(); @@ -214,6 +226,8 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ friendlyName = L"Unnamed Not Present Endpoint"; else friendlyName = std::wstring(pv.pwszVal); + + this->setFlow(); } /* @@ -222,13 +236,27 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ void Endpoint::activateEndpointVolume() { - if (this->endpointVolume == nullptr) - if(FAILED(endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume))) { log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); }; + //bool extraThread = false; + /* + * Forgive me, for MS has sinned, and now I must too. + */ + if (this->endpointVolume == nullptr){ + HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume); + //if (endpointVolume == nullptr) { //why they returning 0 after dealing with the error jfc CO_E_NOTINITIALIZED) { + //CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + //extraThread = true; + //goto initialized; + //} + //log_debugcpp(std::string("no endpointVolume (IAudioEndpointVolume)")); + if (result == S_OK) + CoUninitialize(); + } } void Endpoint::reloadEndpointChannels() { if (this->endpointState == DEVICE_STATE_ACTIVE) { - if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {};/* log_debugcpp("get channel count fail"); */ + if (FAILED(endpointVolume->GetChannelCount(&channelCount))) {log_debugcpp("get channel count fail");};/* */ } } @@ -272,8 +300,10 @@ bool Endpoint::getMute(){ void Endpoint::setState(uint8_t state){ this->endpointState = state; - if(state == EndpointState::ENDPOINT_ACTIVE) + if(state == EndpointState::ENDPOINT_ACTIVE) { + this->activateEndpointVolume(); this->reloadEndpointChannels(); + } } size_t Endpoint::getState(){ @@ -468,7 +498,7 @@ void Overseer::reloadEndpoints(Flows flow) { for (unsigned int i = 0; i < numEndpoints; i++){ if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; Endpoint *endpoint = new Endpoint(temp, i); - endpoint->setFlow(); + //endpoint->setIndex(i); if (flow == Flows::FLOW_PLAYBACK) this->playbackDevices.push_back(endpoint); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 937ee67..ef2cbfb 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -14,15 +14,14 @@ EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { //epName = ep->getName(); this->setBackEndpointVolumeCallbackInfoContent(this->getState()); osh->pushBackEndpointHandler(this, flow); - } void OverseerHandler::pushBackEndpointHandler(EndpointHandler* eph, Flows flow) { if (eph == nullptr) return; - if (flow = Flows::FLOW_PLAYBACK) - playbackEndpointHandlers.push_back(eph); + if (flow == Flows::FLOW_PLAYBACK) + this->playbackEndpointHandlers.push_back(eph); else - captureEndpointHandlers.push_back(eph); + this->captureEndpointHandlers.push_back(eph); return; } @@ -39,6 +38,10 @@ EndpointState EndpointHandler::getFrontVisibilityState(){ return ephfv.visibility; } +Flows EndpointHandler::getFlow(){ + return ep->getFlow(); +} + /* these two, currently unused. If I use them, I should feel bad. * Endpoint* EndpointHandler::getEndpoint() { * return this->ep; @@ -212,16 +215,16 @@ void OverseerHandler::reloadEndpointHandlers(){ //setEndpointHandlers(ephs); } -EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId){ - Flows flow; - Endpoint* newEp = this->os->addEndpoint(endpointId, &flow); +EndpointHandler* OverseerHandler::addEndpoint(std::wstring endpointId, /* out */ Flows *flow = nullptr){ + Flows localFlow; + Endpoint* newEp = this->os->addEndpoint(endpointId, &localFlow); - uint64_t ephIdx = (flow == Flows::FLOW_PLAYBACK ? this->getPlaybackEndpointsCount() : this->getCaptureEndpointsCount()); + uint64_t ephIdx = (localFlow == Flows::FLOW_PLAYBACK ? this->getPlaybackEndpointsCount() : this->getCaptureEndpointsCount()) - 1; - EndpointHandler* newEph = new EndpointHandler(ephIdx, flow); + EndpointHandler* newEph = new EndpointHandler(ephIdx, localFlow); // std::vector getPlaybackEndpointHandlers(); //std::vector getCaptureEndpointHandlers(); - + if (flow != nullptr) *flow = localFlow; return newEph; } @@ -238,24 +241,34 @@ void OverseerHandler::changeFrontDefaultsCallback(Roles role, std::wstring endpo } void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointState state) { + std::vector allHandlers; + allHandlers.insert(allHandlers.end(), this->captureEndpointHandlers.begin(), this->captureEndpointHandlers.end()); + allHandlers.insert(allHandlers.end(), this->playbackEndpointHandlers.begin(), this->playbackEndpointHandlers.end()); EndpointHandler* eph = nullptr; - for (auto loopEph : this->playbackEndpointHandlers) { + for (auto loopEph : allHandlers) { if (loopEph->getId() == endpointId) { eph = loopEph; break; } } + //debug + Flows flow; if (!eph) { - - } + if (state ^ EndpointState::ENDPOINT_ACTIVE) return; + //return; + //flow = Flows::FLOW_CAPTURE; + eph = osh->addEndpoint(endpointId, &flow); + } else + flow = eph->getFlow(); - //todo: testing missing shiez. sowwy - //if(!eph) osh-> + //todo: mic done but disabled. Tab-kun will come... + if (flow == Flows::FLOW_CAPTURE) return; - if(EndpointState::ENDPOINT_ACTIVE & state) { + + if(eph && EndpointState::ENDPOINT_ACTIVE & state) { this->addEndpointWidget(eph); - } else if (eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ + } else if (eph && eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE){ this->removeEndpointWidget(eph->getFrontVisibilityIndex()); } return; diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index b64389d..85ecff9 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -134,7 +134,7 @@ public: uint64_t getPlaybackEndpointsCount(); uint64_t getCaptureEndpointsCount(); void reloadEndpointHandlers(); - EndpointHandler* addEndpoint(std::wstring endpointId); + EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); NGuid getGuid(); private: diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index da01a60..d47bd72 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -85,7 +85,8 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare /* * Channel sliders setup */ - for(uint32_t i = 0; i < eph->getChannelCount(); i++){ + uint32_t epChannelCount = eph->getChannelCount(); + for(uint32_t i = 0; i < epChannelCount && epChannelCount > 1; i++){ QSlider* tmp = new QSlider(Qt::Horizontal); QLabel* tmpLb = new QLabel(""); tmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -163,7 +164,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider->setValue((int)((eph->getCallbackInfo()->mainVolume + roundingFactor) * 100)); muteButton->setCheckState((eph->getCallbackInfo()->muted == false ? Qt::Unchecked : Qt::Checked)); muteButton->setText(eph->getCallbackInfo()->muted ? STRING_UNMUTE : STRING_MUTE); - for(uint32_t i = 0; i < eph->getCallbackInfo()->channels; i++){ + for(uint32_t i = 0; i < eph->getCallbackInfo()->channels && eph->getChannelCount() > 1; i++){ this->channelSliders.at(i)->blockSignals(true); this->channelSliders.at(i)->setValue((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100)); this->channelLabels.at(i)->setText(QString::number((int)((eph->getCallbackInfo()->channelVolumes[i] + roundingFactor) * 100))); @@ -210,15 +211,20 @@ void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->layout->removeWidget(ews.at(i)); - uint64_t saisu = ews.size(); + //uint64_t saisu = ews.size(); //delete ews.at(index); delete ews.at(i); - while ((i + 1) < ews.size()) { - ews.at(i) = ews.at(i + 1); - ews.at(i)->setIndex(i); - i++; - } - ews.pop_back(); + ews.at(i) = nullptr; + this->ewsUpdateTimer->start(); + + /* + * while ((i + 1) < ews.size()) { + * ews.at(i) = ews.at(i + 1); + * ews.at(i)->setIndex(i); + * i++; + * } + * ews.pop_back(); + */ return; } @@ -229,6 +235,34 @@ void MainWindow::addEndpointWidget(EndpointWidgetEvent* ev){ return; } +void MainWindow::reorderEndpointWidgetCollection() { + size_t firstNullPosition = 0; + size_t ewsSize = ews.size(); + bool breakSorting = false; + + //todo: is all of gui really atomic by definition? im afraid of cutting through amazing add momentos, but I think I did my homework. Must check back. + for (size_t i = 0; i < ewsSize; i++) { + if (ews.at(i) == nullptr) { + for (size_t j = (i + 1); j < ewsSize; j++) { + + if (ews.at(j) != nullptr) { + ews.at(i) = ews.at(j); + ews.at(i)->setIndex(i); + ews.at(j) = nullptr; + break; + } + if (j == ewsSize - 1) { + firstNullPosition = i; + breakSorting = true; + } + + } + if (breakSorting) break; + } + } + ews.resize(firstNullPosition + 1); +} + void EndpointWidget::updateMute(int checked){ bool muted = (checked == 2 ? true : false); this->eph->setMute(osh->getGuid(), muted); @@ -304,13 +338,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QEvent::registerEventType(CustomQEvent::EndpointWidgetObsolete); QEvent::registerEventType(CustomQEvent::EndpointWidgetCreated); QEvent::registerEventType(CustomQEvent::EndpointDefaultChange); - + + ewsUpdateTimer = new QTimer(this); widget = new QWidget(); layout = new QGridLayout(); trayIcon = new QSystemTrayIcon(); trayIconMenu = new QMenu(); trayIconMenuQuit = new QAction(STRING_QUIT); + ewsUpdateTimer->setSingleShot(true); + ewsUpdateTimer->setInterval(ewsUpdateTimerFrequency); + connect(ewsUpdateTimer, &QTimer::timeout, this, &MainWindow::reorderEndpointWidgetCollection); widget->setLayout(layout); setCentralWidget(widget); //layout->addWidget(pintas, 0, 0); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 9a40d39..6f1a51e 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -150,6 +150,7 @@ private slots: void trayIconActivated(QSystemTrayIcon::ActivationReason reason); void removeEndpointWidget(EndpointWidgetEvent* ev); void addEndpointWidget(EndpointWidgetEvent* ev); + void reorderEndpointWidgetCollection(); //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); @@ -162,7 +163,8 @@ private: QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; QAction *trayIconMenuQuit; - + QTimer *ewsUpdateTimer; + static constexpr uint64_t ewsUpdateTimerFrequency = 500; //public slots: // void setEndpointHandlers(std::vector *ephs); From 6d88697811527ea9e43153ea9030f206b5d9b47e Mon Sep 17 00:00:00 2001 From: Hane Date: Mon, 18 Sep 2023 23:37:06 +0200 Subject: [PATCH 52/57] session baby steps --- qtest.pro | 4 +- src/back/backfuncs.h | 4 +- src/back/backlasses.cpp | 39 +++++++++++++++- src/back/backlasses.h | 39 ++++++---------- src/back/backsessionclasses.cpp | 62 +++++++++++++++++++++++++ src/back/backsessionclasses.h | 26 +++++++++++ src/back/msinclude.h | 25 ++++++++++ src/cont/contclasses.cpp | 21 +++++++++ src/cont/contclasses.h | 55 +++++----------------- src/cont/contsessionclasses.cpp | 30 ++++++++++++ src/cont/contsessionclasses.h | 23 +++++++++ src/global.h | 48 +++++++++++++++++++ src/qt/qtclasses.cpp | 82 ++++++++++++++++++++++++++++----- src/qt/qtclasses.h | 22 ++++++++- 14 files changed, 393 insertions(+), 87 deletions(-) create mode 100644 src/back/backsessionclasses.cpp create mode 100644 src/back/backsessionclasses.h create mode 100644 src/back/msinclude.h create mode 100644 src/cont/contsessionclasses.cpp create mode 100644 src/cont/contsessionclasses.h diff --git a/qtest.pro b/qtest.pro index dd5e215..046d8d8 100644 --- a/qtest.pro +++ b/qtest.pro @@ -9,8 +9,8 @@ QT += widgets network INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" DESTPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\cont" -SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp contclasses.cpp -HEADERS += qtclasses.h backlasses.h contclasses.h global.h debug.h backfuncs.h ipolicyconfig.h +SOURCES += qtestmain.cpp qtclasses.cpp backlasses.cpp backsessionclasses.cpp contclasses.cpp contsessionclasses.cpp +HEADERS += qtclasses.h backlasses.h backsessionclasses.h contclasses.h contsessionclasses.h global.h debug.h backfuncs.h ipolicyconfig.h msinclude.h RESOURCES = assets.qrc #DESTDIR += "build" diff --git a/src/back/backfuncs.h b/src/back/backfuncs.h index 1062ba1..b8e1c6c 100644 --- a/src/back/backfuncs.h +++ b/src/back/backfuncs.h @@ -1,4 +1,4 @@ -GUID NGuidToGUID(NGuid guid) { +GUID inline NGuidToGUID(NGuid guid) { GUID msGuid = GUID(); msGuid.Data1 = guid.data1; msGuid.Data2 = guid.data2; @@ -15,7 +15,7 @@ GUID NGuidToGUID(NGuid guid) { return msGuid; } -NGuid GUIDToNGuid(LPGUID msGuid){ +NGuid inline GUIDToNGuid(LPGUID msGuid){ NGuid guid = NGuid(); guid.data1 = msGuid->Data1; guid.data2 = msGuid->Data2; diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 66a8015..dea09b6 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -207,7 +207,7 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ reloadEndpointChannels(); - /* + /* todo: check header * if(FAILED(endpoint->Activate(__uuidof(IAudioMeterInformation), * CLSCTX_ALL, NULL, (void**)&endpointPeakMeter))) { log_debugcpp("peakbros..."); } */ @@ -228,12 +228,37 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ friendlyName = std::wstring(pv.pwszVal); this->setFlow(); + if (this->endpointState == EndpointState::ENDPOINT_ACTIVE && this->flow == Flows::FLOW_PLAYBACK) { + activateEndpointSessions(); + } } /* * Endpoint::Endpoint(IMMDevice* endpoint) : Endpoint(endpoint, 0) {}; */ +void Endpoint::activateEndpointSessions() { + //sessionManager; + if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**) &sessionManager))) { log_wdebugcpp(L"sesionbros..."); return; } + + IAudioSessionEnumerator* sessionEnumerator = nullptr; + if (FAILED(sessionManager->GetSessionEnumerator(&sessionEnumerator))) { log_wdebugcpp(L"sesEnumeratorBros..."); return; } + + int sessionCount; + sessionEnumerator->GetCount(&sessionCount); + for (int i = 0; i < sessionCount; i++) { + IAudioSessionControl* sessionControlTmp; + sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp); + //todo:: asegurar lo del dynamic_cast + IAudioSessionControl2* sessionControl; + sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl); + sessionControl->AddRef(); + sessionControlTmp->Release(); + Session* session = new Session(this, sessionControl, (size_t)i); + endpointSessions.push_back(session); + } + sessionEnumerator->Release(); +} void Endpoint::activateEndpointVolume() { //bool extraThread = false; @@ -444,11 +469,21 @@ Flows Endpoint::getFlow() { return this->flow; } +/* sessions */ +std::vector Endpoint::getSessions() { + return endpointSessions; +} + +size_t Endpoint::getSessionCount() { + return endpointSessions.size(); +} + Endpoint::~Endpoint(){ - log_debugcpp("murio endpoint-san uwu"); + log_wdebugcpp(L"murio endpoint-san uwu"); properties->Release(); endpointVolume->Release(); endpoint->Release(); + sessionManager->Release(); } void Overseer::initCOMLibrary() { diff --git a/src/back/backlasses.h b/src/back/backlasses.h index ebfc07e..453e7a5 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -1,33 +1,12 @@ #pragma once -#define _WIN32_WINNT 0x0A00 -#include - -//done by qt by def #define UNICODE - -#include -#include -#include -#include -#include -#include -#include -//#include - -#include -#include -#include -//#include -//#include -#include -#include -#include "ipolicyconfig.h" -#include - +#include "msinclude.h" +#include "backsessionclasses.h" #include "global.h" #include "contclasses.h" class EndpointVolumeCallback; +class Session; class Endpoint { @@ -53,15 +32,25 @@ class Endpoint { Flows getFlow(); std::wstring getId(); std::wstring getName(); + + void setVolumeCallback(EndpointVolumeCallback *epc); void removeVolumeCallback(EndpointVolumeCallback *epc); + + /* sessions */ + std::vector getSessions(); + size_t getSessionCount(); + ~Endpoint(); private: void inline activateEndpointVolume(); - + void inline activateEndpointSessions(); + + std::vector endpointSessions; uint32_t channelCount = 0; IMMDevice* endpoint; + IAudioSessionManager2 *sessionManager; Flows flow; IAudioEndpointVolume *endpointVolume = nullptr; IPropertyStore *properties; diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp new file mode 100644 index 0000000..fce5ad8 --- /dev/null +++ b/src/back/backsessionclasses.cpp @@ -0,0 +1,62 @@ +#include "backsessionclasses.h" +#include "backfuncs.h" +Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx) { + this->ep = ep; + this->sessionControl = sessionControl; + this->idx = idx; + + sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); + DWORD pid; + sessionControl->GetProcessId(&pid); + if (sessionControl->IsSystemSoundsSession() == S_OK) + this->sessionName = std::wstring(LSTRING_SYSTEM_SOUNDS); + else { + LPWSTR sessionDisplayName; + this->sessionControl->GetDisplayName(&sessionDisplayName); + this->sessionName = std::wstring(sessionDisplayName); + CoTaskMemFree(sessionDisplayName); + } +} + +float Session::getVolume(int channel){ + float volume; + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(sessionVolume->GetMasterVolume(&volume))) { /* log_debugcpp("si") */;} + } else { + return 0.0; + //if(FAILED(endpointVolume->GetChannelVolumeLevelScalar(channel, &volume))) { /* log_debugcpp("si"); */} + } + return volume; +} + +/* + * uint32_t Endpoint::getChannelCount(){ + * return (uint32_t)channelCount; + * } + */ + +std::wstring Session::getName() { + return sessionName; +} + +bool Session::getMute() { + BOOL mut; + if(FAILED(sessionVolume->GetMute(&mut))) { /* TIP: Below */ } + bool mute = (bool)mut; + return mute; +} + +void Session::setVolume(NGuid guid, int channel, float volume) { + //TIP: There used to be log messages here. Now, it's a ghost town. + GUID tempMsGuid = NGuidToGUID(guid); + if (channel == AudioChannel::CHANNEL_MAIN) { + if(FAILED(sessionVolume->SetMasterVolume(volume, &tempMsGuid))) {}; + } else { + //if(FAILED(sessionVolume->SetChannelVolumeLevelScalar(channel, volume, &tempMsGuid))) {}; + } +} + +void Session::setMute(NGuid guid, bool muted) { + GUID tempMsGuid = NGuidToGUID(guid); + if(FAILED(sessionVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"SessionVolume null?")); }; +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h new file mode 100644 index 0000000..56e4cfc --- /dev/null +++ b/src/back/backsessionclasses.h @@ -0,0 +1,26 @@ +#pragma once +#include "msinclude.h" + +#include "global.h" +#include "contclasses.h" + +class Endpoint; + +class Session { + + public: + Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx); + void setVolume(NGuid guid, int channel, float volume); + float getVolume(int channel); + void setMute(NGuid guid, bool muted); + bool getMute(); + std::wstring getName(); + //uint32_t getChannelCount(); + + private: + std::wstring sessionName; + Endpoint* ep; + IAudioSessionControl2* sessionControl = nullptr; + ISimpleAudioVolume* sessionVolume = nullptr; + size_t idx; +}; diff --git a/src/back/msinclude.h b/src/back/msinclude.h new file mode 100644 index 0000000..6709400 --- /dev/null +++ b/src/back/msinclude.h @@ -0,0 +1,25 @@ +#pragma once + +#define _WIN32_WINNT 0x0A00 +#include + +//done by qt by def #define UNICODE + +#include +#include +#include +#include +#include +#include +#include +//#include + +#include +#include +#include +//#include +//#include +#include +#include +#include "ipolicyconfig.h" +#include diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index ef2cbfb..653517b 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -14,6 +14,14 @@ EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { //epName = ep->getName(); this->setBackEndpointVolumeCallbackInfoContent(this->getState()); osh->pushBackEndpointHandler(this, flow); + + + if (this->flow == Flows::FLOW_PLAYBACK && this->getState() == EndpointState::ENDPOINT_ACTIVE) { + 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) { @@ -141,6 +149,19 @@ void EndpointHandler::removeRoles(Roles newRole){ ep->removeRoles(newRole); } +/* sessions */ +size_t EndpointHandler::getSessionCount() { + return ep->getSessionCount(); +} + +std::vector EndpointHandler::getSessions(){ + return ep->getSessions(); +} + +std::vector EndpointHandler::getSessionHandlers(){ + return this->sessionHandlers; +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); epc->Release(); diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 85ecff9..a89a42a 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -1,4 +1,7 @@ #pragma once + +#include "global.h" +#include "contsessionclasses.h" //#define invoke_mem_fn(object,ptrToMember) ((object).*(ptrToMember)) //#define pinvoke_mem_fn(object,ptrToMember) ((object)->*(ptrToMember)) @@ -6,50 +9,7 @@ class EndpointWidget; class Endpoint; class EndpointVolumeCallback; class Overseer; - -enum AudioChannel { - CHANNEL_LEFT = (1 << 0), - CHANNEL_RIGHT = (1 << 1), - CHANNEL_MAIN = ~0, -}; - -enum EndpointState { - ENDPOINT_ACTIVE = (1 << 0), - ENDPOINT_DISABLED = (1 << 1), - ENDPOINT_NOTPRESENT = (1 << 2), - ENDPOINT_UNPLUGGED = (1 << 3), - ENDPOINT_ALL = 0x0F -}; - -enum Flows { - FLOW_PLAYBACK = (1 << 0), - FLOW_CAPTURE = (1 << 1), - FLOW_BOTH = (1 << 2), -}; - -enum Roles { - ROLE_CONSOLE = (1 << 0), - ROLE_MULTIMEDIA = (1 << 1), - ROLE_COMMUNICATIONS = (1 << 2), - ROLE_ALL = 0x07, -}; - -struct NGuid { - //todo: still leaking? - uint32_t data1; - uint16_t data2; - uint16_t data3; - unsigned char data4[8]; - - - /* void freeData4(){ */ - /* int i = 0; */ - /* do{ */ - /* if(this->data4 + i != nullptr) free(data4 + i); */ - /* i++; */ - /* }while (i < 8); */ - /* } */ -}; +class SessionHandler; struct BackEndpointVolumeCallbackInfo { NGuid caller; @@ -98,8 +58,14 @@ public: void setState(uint8_t state); void setState(uint8_t state, uint64_t idx); + /* sessions */ + size_t getSessionCount(); + std::vector getSessionHandlers(); + ~EndpointHandler(); private: + std::vector getSessions(); + uint64_t idx; Endpoint *ep = nullptr; EndpointVolumeCallback *epc = nullptr; @@ -110,6 +76,7 @@ private: uint64_t frontIdx = INT_MAX; }; EndpointHandlerFrontVisibility ephfv; + std::vector sessionHandlers; //QSlider *slidy; }; diff --git a/src/cont/contsessionclasses.cpp b/src/cont/contsessionclasses.cpp new file mode 100644 index 0000000..92d6d6a --- /dev/null +++ b/src/cont/contsessionclasses.cpp @@ -0,0 +1,30 @@ +#include "contsessionclasses.h" +#include "backsessionclasses.h" + +SessionHandler::SessionHandler(EndpointHandler* eph, Session* session, size_t idx) { + this->eph = eph; + this->idx = idx; + this->session = session; +} + +void SessionHandler::setVolume(NGuid guid, int channel, int value){ + if (channel == AudioChannel::CHANNEL_MAIN) + session->setVolume(guid, channel, (float)value / 100); + else session->setVolume(guid, channel, (float)value / 100); +} + +float SessionHandler::getVolume(int channel){ + return session->getVolume(channel); +} + +void SessionHandler::setMute(NGuid guid, bool muted){ + session->setMute(guid, muted); +} + +std::wstring SessionHandler::getName(){ + return session->getName(); +} + +bool SessionHandler::getMute(){ + return session->getMute(); +} diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h new file mode 100644 index 0000000..aea425e --- /dev/null +++ b/src/cont/contsessionclasses.h @@ -0,0 +1,23 @@ +#pragma once + +#include "global.h" +//#include "contclasses.h" + +class EndpointHandler; +class Session; + +class SessionHandler { + public: + SessionHandler(EndpointHandler* eph, Session* session, size_t idx); + void setVolume(NGuid guid, int channel, int value); + float getVolume(int channel); + void setMute(NGuid guid, bool muted); + bool getMute(); + std::wstring getName(); + + private: + EndpointHandler* eph; + Session* session; + size_t idx; + +}; diff --git a/src/global.h b/src/global.h index 0d7de5e..008fbc5 100644 --- a/src/global.h +++ b/src/global.h @@ -18,8 +18,56 @@ #define STRING_ROLE_MULTIMEDIA "Multimedia" #define STRING_ROLE_COMMUNICATIONS "Communications" #define STRING_ROLE_ALL "All" + +#define STRING_SYSTEM_SOUNDS "System Sounds" +#define LSTRING_SYSTEM_SOUNDS L"System Sounds" //INIT BACK +enum AudioChannel { + CHANNEL_LEFT = (1 << 0), + CHANNEL_RIGHT = (1 << 1), + CHANNEL_MAIN = ~0, +}; + +enum EndpointState { + ENDPOINT_ACTIVE = (1 << 0), + ENDPOINT_DISABLED = (1 << 1), + ENDPOINT_NOTPRESENT = (1 << 2), + ENDPOINT_UNPLUGGED = (1 << 3), + ENDPOINT_ALL = 0x0F +}; + +enum Flows { + FLOW_PLAYBACK = (1 << 0), + FLOW_CAPTURE = (1 << 1), + FLOW_BOTH = (1 << 2), +}; + +enum Roles { + ROLE_CONSOLE = (1 << 0), + ROLE_MULTIMEDIA = (1 << 1), + ROLE_COMMUNICATIONS = (1 << 2), + ROLE_ALL = 0x07, +}; + +struct NGuid { + //todo: still leaking? + uint32_t data1; + uint16_t data2; + uint16_t data3; + unsigned char data4[8]; + + + /* void freeData4(){ */ + /* int i = 0; */ + /* do{ */ + /* if(this->data4 + i != nullptr) free(data4 + i); */ + /* i++; */ + /* }while (i < 8); */ + /* } */ +}; + + class OverseerHandler; extern OverseerHandler *osh; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index d47bd72..d2fdf3b 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -25,8 +25,56 @@ void ExtendedCheckBox::customEvent(QEvent* ev) { QCheckBox::customEvent(ev); } +SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) : QWidget(parent){ + //todo: based on qgridlayout, name+mute should be its own widget, same with channels + this->idx = idx; + this->sh = sh; + + layout = new QGridLayout(this); + //this->setLayout( + + muteButton = new QCheckBox(this); + mainLabel = new QLabel(QString::fromStdWString(sh->getName()), this); + mainSlider = new QSlider(Qt::Horizontal, this); + + mainSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mainSlider->setFocusPolicy(Qt::StrongFocus); + mainSlider->setTickPosition(QSlider::TicksBothSides); + mainSlider->setTickInterval(5); + mainSlider->setSingleStep(1); + mainSlider->setRange(0,100); + + muteButton->setCheckState((sh->getMute() == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(sh->getMute() ? STRING_UNMUTE : STRING_MUTE); + float volume = sh->getVolume(AudioChannel::CHANNEL_MAIN) * 100; + mainSlider->setValue((int)volume); + log_debugcpp("SESSION SET WITH VOLUME " + std::to_string(volume)); + + //tip: would need to be new widget with layout in it + //mainMuteLayout = new QGridLayout(); + layout->addWidget(mainLabel, 0, 0, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(muteButton, 0, 1, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(mainSlider, 0, 2, 1, 2); + + //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? + connect(mainSlider, &QSlider::valueChanged, this,&SessionWidget::updateMainVolume); + connect(muteButton, &QCheckBox::stateChanged, this, (&SessionWidget::updateMute)); + +} + +void SessionWidget::updateMute(int checked){ + bool muted = (checked == 2 ? true : false); + this->sh->setMute(osh->getGuid(), muted); + this->muteButton->setText(this->sh->getMute() ? STRING_UNMUTE : STRING_MUTE); +} + +void SessionWidget::updateMainVolume(int newValue){ + this->sh->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); +} + EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ //todo: based on qgridlayout, name+mute should be its own widget, same with channels + int row = 0; this->idx = idx; this->eph = eph; //todo: sussy @@ -52,7 +100,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare mainSlider = new QSlider(Qt::Horizontal, this); if (this->eph->getState() != EndpointState::ENDPOINT_ACTIVE) { - layout->addWidget(mainLabel, 0, 0); + layout->addWidget(mainLabel, row, 0); layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); return; } @@ -73,9 +121,10 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare //tip: would need to be new widget with layout in it //mainMuteLayout = new QGridLayout(); - layout->addWidget(mainLabel, 0, 0, Qt::AlignLeft | Qt::AlignBottom); - layout->addWidget(muteButton, 0, 1, Qt::AlignLeft | Qt::AlignBottom); - layout->addWidget(mainSlider, 0, 2, 1, 2); + layout->addWidget(mainLabel, row, 0, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(muteButton, row, 1, Qt::AlignLeft | Qt::AlignBottom); + layout->addWidget(mainSlider, row, 2, 1, 2); + row++; //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? connect(mainSlider, &QSlider::valueChanged, this,&EndpointWidget::updateMainVolume); @@ -99,8 +148,9 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare tmpLb->setText(QString::number(volume)); this->channelSliders.push_back(tmp); this->channelLabels.push_back(tmpLb); - layout->addWidget(tmp, 2, i); - layout->addWidget(tmpLb, 3, i); + layout->addWidget(tmp, row + 1, i); + layout->addWidget(tmpLb, row + 2, i); + //TODO: check if there's a need to prevent deadlocks; probably this will eventually turn into its own func //this causes channel bar desync when back -> front. blocksignals below fix it. huh. connect(tmp, &QSlider::valueChanged, [this, i](int newValue){ @@ -108,6 +158,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->channelLabels.at(i)->setText(QString::number(newValue)); }); } + row += 3; /* * Role ExtendedCheckBoxes setup @@ -145,11 +196,11 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare this->eph->setRoles(Roles::ROLE_COMMUNICATIONS); }); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), 5, 0); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), 5, 1); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), 5, 2); - layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), 5, 3); - + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_ALL), row, 0); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_CONSOLE), row, 1); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_MULTIMEDIA), row, 2); + layout->addWidget(defaultRolesCheckBoxes.at(Roles::ROLE_COMMUNICATIONS), row, 3); + row++; /* ----------------------------------------------------------- */ /* @@ -158,6 +209,7 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare timer = new QTimer(); connect(timer, &QTimer::timeout, [this, eph](){ //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. + //todo: global + constexpr + ratio const float roundingFactor = 0.005; mainSlider->blockSignals(true); muteButton->blockSignals(true); @@ -179,6 +231,14 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare muteButton->blockSignals(false); }); timer->start(10); + + /* sessions */ + for (size_t i = 0; i < eph->getSessionCount(); i++) { + SessionWidget* sessionWidget = new SessionWidget(i, eph->getSessionHandlers().at(i), this); + layout->addWidget(sessionWidget, row, 4); + row++; + sessionWidgets.push_back(sessionWidget); + } //todo parent? layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6f1a51e..3afb63f 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -85,6 +85,25 @@ public: }; +class SessionWidget : public QWidget { +Q_OBJECT + +public: + SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent /* = nullptr */); + +public slots: + void updateMainVolume(int newValue); + void updateMute(int checked); + +private: + QLabel *mainLabel = nullptr; + QSlider *mainSlider = nullptr; + uint64_t idx; + QGridLayout *layout = nullptr; + QCheckBox *muteButton = nullptr; + SessionHandler* sh; +}; + class EndpointWidget : public QWidget { Q_OBJECT @@ -120,12 +139,13 @@ private: QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; std::map defaultRolesCheckBoxes; - + EndpointHandler* eph; size_t defaultRolesVectorSize = 4; QTimer* timer = nullptr; uint64_t idx; + std::vector sessionWidgets; //std::vector *ephs; //std::vector *sliders; From cb9446cb421ac7e326552b824284cdf03ca3ed1b Mon Sep 17 00:00:00 2001 From: Hane Date: Tue, 19 Sep 2023 16:05:39 +0200 Subject: [PATCH 53/57] temp commit --- src/back/backsessionclasses.cpp | 89 ++++++++++++++++++++++++++++++++- src/back/backsessionclasses.h | 1 + src/back/msinclude.h | 1 + src/global.h | 1 + 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp index fce5ad8..7349c19 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -1,5 +1,6 @@ #include "backsessionclasses.h" #include "backfuncs.h" + Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx) { this->ep = ep; this->sessionControl = sessionControl; @@ -13,7 +14,10 @@ Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx else { LPWSTR sessionDisplayName; this->sessionControl->GetDisplayName(&sessionDisplayName); - this->sessionName = std::wstring(sessionDisplayName); + if (!wcscmp(sessionDisplayName, L"")) + this->sessionName = this->fetchProcessName(pid); + else + this->sessionName = std::wstring(sessionDisplayName); CoTaskMemFree(sessionDisplayName); } } @@ -60,3 +64,86 @@ void Session::setMute(NGuid guid, bool muted) { GUID tempMsGuid = NGuidToGUID(guid); if(FAILED(sessionVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"SessionVolume null?")); }; } + +std::wstring Session::fetchProcessName(DWORD pid) { + /* + * https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot + * https://stackoverflow.com/questions/11843368/how-to-get-process-description + */ + + /* Executable path retrieval */ + std::wstring exePath = L""; + + HANDLE processList = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); + if (processList == INVALID_HANDLE_VALUE) { + log_wdebugcpp(L"aye no procname."); + return exePath; + } + + MODULEENTRY32W me32w; + me32w.dwSize = sizeof(MODULEENTRY32W); + if(Module32FirstW(processList, &me32w)) { + do { + if (me32w.th32ProcessID == pid) { + exePath = std::wstring(me32w.szExePath); + break; + /* + * However, if the calling process is a 32-bit process, you must call the + * QueryFullProcessImageName function to retrieve the full path of the + * executable file for a 64-bit process. + */ + } + } while(Module32NextW(processList, &me32w)); + } + CloseHandle(processList); + + /* File description retrieval */ + struct LANGANDCODEPAGE { + WORD wLanguage; + WORD wCodePage; + } *translationArray; + + DWORD filler; + DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(exePath.c_str(), &filler); + if (!fileVersionInfoSize) return exePath; + + void* fileVersionInfo = malloc(fileVersionInfoSize); + if(!GetFileVersionInfoW(exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) + return exePath; + + UINT translationArrayLen = 0; + if (!VerQueryValueW(fileVersionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&translationArray, &translationArrayLen)) + return exePath; + + bool match = false; + for (UINT i = 0; i < (translationArrayLen / sizeof(LANGANDCODEPAGE)); i++) { + wchar_t fileDescriptionKey[256]; + LANGID defaultUILanguage = GetUserDefaultUILanguage(); + if (defaultUILanguage != translationArray[i].wLanguage) + continue; + + match = true; + wchar_t* fileDescription = NULL; + UINT fileDescriptionSize = 0; + swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", + translationArray[i].wLanguage, translationArray[i].wCodePage); + if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { + exePath = std::wstring(fileDescription); + } + } + + if (!match && 1 <= (translationArrayLen / sizeof(LANGANDCODEPAGE))) { + wchar_t fileDescriptionKey[256]; + +wchar_t* fileDescription = NULL; + UINT fileDescriptionSize = 0; + swprintf(fileDescriptionKey, L"\\StringFileInfo\\%04x%04x\\FileDescription", + translationArray[0].wLanguage, translationArray[0].wCodePage); + if (VerQueryValueW(fileVersionInfo, fileDescriptionKey, (LPVOID*)&fileDescription, &fileDescriptionSize)) { + exePath = std::wstring(fileDescription); + } + } + + free(fileVersionInfo); + return exePath; +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h index 56e4cfc..369dcab 100644 --- a/src/back/backsessionclasses.h +++ b/src/back/backsessionclasses.h @@ -18,6 +18,7 @@ class Session { //uint32_t getChannelCount(); private: + std::wstring fetchProcessName(DWORD pid); std::wstring sessionName; Endpoint* ep; IAudioSessionControl2* sessionControl = nullptr; diff --git a/src/back/msinclude.h b/src/back/msinclude.h index 6709400..fc7beac 100644 --- a/src/back/msinclude.h +++ b/src/back/msinclude.h @@ -23,3 +23,4 @@ #include #include "ipolicyconfig.h" #include +#include diff --git a/src/global.h b/src/global.h index 008fbc5..1c72212 100644 --- a/src/global.h +++ b/src/global.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "debug.h" From 064c16d9e7a29a1f72cc4cfe1b0a2b89b28abc9f Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 3 Feb 2024 15:33:59 +0100 Subject: [PATCH 54/57] session volume/mute polling --- src/back/backlasses.cpp | 39 ++++++++++ src/back/backlasses.h | 14 +++- src/back/backsessionclasses.cpp | 128 ++++++++++++++++++++++++++++++++ src/back/backsessionclasses.h | 24 ++++++ src/cont/contclasses.cpp | 1 - src/cont/contclasses.h | 9 +++ src/cont/contsessionclasses.cpp | 11 +++ src/cont/contsessionclasses.h | 14 ++++ src/qt/qtclasses.cpp | 25 ++++++- src/qt/qtclasses.h | 2 +- 10 files changed, 263 insertions(+), 4 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index dea09b6..04aa309 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -1,6 +1,45 @@ #include #include +EndpointNewSessionCallback::EndpointNewSessionCallback(EndpointHandler* eph){ + this->eph = eph; +} + +ULONG EndpointNewSessionCallback::AddRef(){ + return InterlockedIncrement(&ref); +} + +ULONG EndpointNewSessionCallback::Release(){ + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT EndpointNewSessionCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioSessionNotification) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT EndpointNewSessionCallback::OnSessionCreated(IAudioSessionControl *NewSession) { + return S_OK; +} + EndpointVolumeCallback::EndpointVolumeCallback(Endpoint* ep){ this->ep = ep; } diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 453e7a5..bda4a23 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -32,7 +32,6 @@ class Endpoint { Flows getFlow(); std::wstring getId(); std::wstring getName(); - void setVolumeCallback(EndpointVolumeCallback *epc); void removeVolumeCallback(EndpointVolumeCallback *epc); @@ -134,3 +133,16 @@ class Overseer { //std::vector *captureDevices; }; +class EndpointNewSessionCallback : public IAudioSessionNotification { + public: + //EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); + EndpointNewSessionCallback(EndpointHandler *eph); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnSessionCreated(IAudioSessionControl *NewSession); + + private: + ULONG ref = 1; + EndpointHandler *eph; +}; diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp index 7349c19..a0a4b5e 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -1,6 +1,126 @@ #include "backsessionclasses.h" #include "backfuncs.h" +SessionStateCallback::SessionStateCallback(SessionHandler *sh) { + this->sh = sh; +} + +ULONG SessionStateCallback::AddRef() { + return InterlockedIncrement(&ref); +} + +ULONG SessionStateCallback::Release() { + ULONG tempRef = InterlockedDecrement(&ref); + if (tempRef == 0) { + delete this; + } + return tempRef; +} + +HRESULT SessionStateCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { + if (IID_IUnknown == riid) + { + AddRef(); + *ppvInterface = (IUnknown*)this; + } + else if (__uuidof(IAudioSessionNotification) == riid) + { + AddRef(); + *ppvInterface = (IMMNotificationClient*)this; + } + else + { + *ppvInterface = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +HRESULT SessionStateCallback::OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContex) { + return S_OK; +} + +HRESULT SessionStateCallback::OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext) { + sh->getVolumeInfo()->muted = NewMute; + sh->getVolumeInfo()->mainVolume = NewVolume; + sh->getVolumeInfo()->caller = GUIDToNGuid((LPGUID)EventContext); + /* + * if (NewMute) + * { + * printf("MUTE\n"); + * } + * else + * { + * printf("Volume = %d percent\n", + * (UINT32)(100*NewVolume + 0.5)); + * } + */ + return S_OK; +} + +HRESULT SessionStateCallback::OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext) { + return S_OK; +} + +HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { + /* + * char *pszState = "?????"; + * + * switch (NewState) + * { + * case AudioSessionStateActive: + * pszState = "active"; + * break; + * case AudioSessionStateInactive: + * pszState = "inactive"; + * break; + * } + * printf("New session state = %s\n", pszState); + */ + + return S_OK; +} + +HRESULT SessionStateCallback::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) { + /* + * char *pszReason = "?????"; + * + * switch (DisconnectReason) + * { + * case DisconnectReasonDeviceRemoval: + * pszReason = "device removed"; + * break; + * case DisconnectReasonServerShutdown: + * pszReason = "server shut down"; + * break; + * case DisconnectReasonFormatChanged: + * pszReason = "format changed"; + * break; + * case DisconnectReasonSessionLogoff: + * pszReason = "user logged off"; + * break; + * case DisconnectReasonSessionDisconnected: + * pszReason = "session disconnected"; + * break; + * case DisconnectReasonExclusiveModeOverride: + * pszReason = "exclusive-mode override"; + * break; + * } + * printf("Audio session disconnected (reason: %s)\n", + * pszReason); + */ + + return S_OK; +} + Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx) { this->ep = ep; this->sessionControl = sessionControl; @@ -147,3 +267,11 @@ wchar_t* fileDescription = NULL; free(fileVersionInfo); return exePath; } + +void Session::setStateCallback(SessionStateCallback *ssc){ + sessionControl->RegisterAudioSessionNotification((IAudioSessionEvents*) ssc); +} + +void Session::removeStateCallback(SessionStateCallback *ssc){ + sessionControl->UnregisterAudioSessionNotification((IAudioSessionEvents*) ssc); +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h index 369dcab..3354939 100644 --- a/src/back/backsessionclasses.h +++ b/src/back/backsessionclasses.h @@ -6,6 +6,26 @@ class Endpoint; +class SessionStateCallback : public IAudioSessionEvents { + public: + SessionStateCallback(SessionHandler *sh); + ULONG AddRef(); + ULONG Release(); + HRESULT QueryInterface(REFIID riid, VOID **ppvInterface); + HRESULT OnSessionCreated(IAudioSessionControl *NewSession); + HRESULT OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext); + HRESULT OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext); + HRESULT OnGroupingParamChanged( LPCGUID NewGroupingParam, LPCGUID EventContext); + HRESULT OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext); + HRESULT OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason); + HRESULT OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext); + HRESULT OnStateChanged(AudioSessionState NewState); + + private: + ULONG ref = 1; + SessionHandler *sh; +}; + class Session { public: @@ -15,6 +35,9 @@ class Session { void setMute(NGuid guid, bool muted); bool getMute(); std::wstring getName(); + + void setStateCallback(SessionStateCallback *ssc); + void removeStateCallback(SessionStateCallback *ssc); //uint32_t getChannelCount(); private: @@ -25,3 +48,4 @@ class Session { ISimpleAudioVolume* sessionVolume = nullptr; size_t idx; }; + diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 653517b..a858b0f 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -14,7 +14,6 @@ EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { //epName = ep->getName(); this->setBackEndpointVolumeCallbackInfoContent(this->getState()); osh->pushBackEndpointHandler(this, flow); - if (this->flow == Flows::FLOW_PLAYBACK && this->getState() == EndpointState::ENDPOINT_ACTIVE) { for (int i = 0; i < this->getSessionCount(); i++) { diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index a89a42a..4bdd519 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -103,6 +103,12 @@ public: void reloadEndpointHandlers(); EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); NGuid getGuid(); + + /* Session's */ + /* + * void setSessionVolumeCallback(std::function changeSessionVolume); + * void setSessionVolume(float newValue, ); + */ private: Overseer *os; @@ -111,6 +117,9 @@ private: std::function changeFrontDefaults; std::function removeEndpointWidget; std::function addEndpointWidget; + + /* Session's */ + std::function changeSessionVolume; //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/cont/contsessionclasses.cpp b/src/cont/contsessionclasses.cpp index 92d6d6a..e3f69d0 100644 --- a/src/cont/contsessionclasses.cpp +++ b/src/cont/contsessionclasses.cpp @@ -5,6 +5,13 @@ 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(); + + ssc = new SessionStateCallback(this); + this->session->setStateCallback(ssc); } void SessionHandler::setVolume(NGuid guid, int channel, int value){ @@ -28,3 +35,7 @@ std::wstring SessionHandler::getName(){ bool SessionHandler::getMute(){ return session->getMute(); } + +SessionVolumeInfo* SessionHandler::getVolumeInfo() { + return &svi; +} diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h index aea425e..e637e47 100644 --- a/src/cont/contsessionclasses.h +++ b/src/cont/contsessionclasses.h @@ -5,6 +5,17 @@ class EndpointHandler; class Session; +class SessionStateCallback; + +struct SessionVolumeInfo { + //SessionVolumeInfo(bool muted, float mainVolume); + bool muted; + float mainVolume; + NGuid caller; + + //size_t channels; + //std::vector channelVolumes; +}; class SessionHandler { public: @@ -14,10 +25,13 @@ class SessionHandler { void setMute(NGuid guid, bool muted); bool getMute(); std::wstring getName(); + SessionVolumeInfo* getVolumeInfo(); private: + SessionVolumeInfo svi; EndpointHandler* eph; Session* session; + SessionStateCallback* ssc; size_t idx; }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index d2fdf3b..f420688 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -59,7 +59,30 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) //TODO:0 = mute and muted, change volume = unmuted are client side tricks = 2 callbacks, one for volume, one for mute state. Implement as an user selectable option? connect(mainSlider, &QSlider::valueChanged, this,&SessionWidget::updateMainVolume); connect(muteButton, &QCheckBox::stateChanged, this, (&SessionWidget::updateMute)); - + + /* + * Session Volume Polling + */ + volumePoller = new QTimer(); + connect(volumePoller, &QTimer::timeout, [this, sh](){ + //if (memcmp(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)) == 0) return; CHECK IF THIS PROGRAM GENERATED THE FUNSIES IS NO LONGER IN USE FOR NOW. + //todo: global + constexpr + ratio + const float roundingFactor = 0.005; + mainSlider->blockSignals(true); + muteButton->blockSignals(true); + mainSlider->setValue((int)((sh->getVolumeInfo()->mainVolume + roundingFactor) * 100)); + muteButton->setCheckState((sh->getVolumeInfo()->muted == false ? Qt::Unchecked : Qt::Checked)); + muteButton->setText(sh->getVolumeInfo()->muted ? STRING_UNMUTE : STRING_MUTE); + + //memcpy(osh->callbackInfo[idx]->caller, osh->getGuid(), sizeof(NGuid)); + + //TODO: el default = objcopy frees? + //Todo: like fr pregunta + sh->getVolumeInfo()->caller = osh->getGuid(); + mainSlider->blockSignals(false); + muteButton->blockSignals(false); + }); + volumePoller->start(10); } void SessionWidget::updateMute(int checked){ diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 3afb63f..6cf85b0 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -102,6 +102,7 @@ private: QGridLayout *layout = nullptr; QCheckBox *muteButton = nullptr; SessionHandler* sh; + QTimer* volumePoller = nullptr; }; class EndpointWidget : public QWidget { @@ -118,7 +119,6 @@ public: void setVolume(int channel, float volume); ~EndpointWidget(); - //void updateMainVolume(float newValue); //void updateVolume(uint32_t channel, float newValue); //void updateMute(bool muted); From 574c6f039e1e6753f7f89839abc533b9559c1fa3 Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 3 Feb 2024 17:05:09 +0100 Subject: [PATCH 55/57] temp commit --- src/back/backlasses.cpp | 21 ++++++++++++++++++ src/back/backlasses.h | 2 +- src/back/backsessionclasses.cpp | 38 ++++++++++++++++++++++++++++++++- src/back/backsessionclasses.h | 6 ++++++ src/cont/contclasses.cpp | 13 +++++++++++ src/cont/contclasses.h | 5 +++++ src/global.h | 25 ++++++++++++++-------- 7 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 04aa309..677ce28 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -37,6 +37,22 @@ 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->addSession(newSession); + } + + if (result == S_OK) + CoUninitialize(); + return S_OK; } @@ -299,6 +315,11 @@ void Endpoint::activateEndpointSessions() { sessionEnumerator->Release(); } +void Endpoint::addSession(Session* session) { + session->setIndex(this->getSessionCount()); + endpointSessions.push_back(session); +} + void Endpoint::activateEndpointVolume() { //bool extraThread = false; /* diff --git a/src/back/backlasses.h b/src/back/backlasses.h index bda4a23..b95be81 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -39,6 +39,7 @@ class Endpoint { /* sessions */ std::vector getSessions(); size_t getSessionCount(); + void addSession(Session* session); ~Endpoint(); @@ -135,7 +136,6 @@ class Overseer { class EndpointNewSessionCallback : public IAudioSessionNotification { public: - //EndpointSituationCallback(IMMDeviceEnumerator *deviceEnumerator, std::vector playbackDevices); EndpointNewSessionCallback(EndpointHandler *eph); ULONG AddRef(); ULONG Release(); diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp index a0a4b5e..7f3b101 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -71,6 +71,11 @@ HRESULT SessionStateCallback::OnGroupingParamChanged(LPCGUID NewGroupingParam, L } HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { + /* enum _AudioSessionState { + * AudioSessionStateInactive, + * AudioSessionStateActive, + * AudioSessionStateExpired + * } AudioSessionState; */ /* * char *pszState = "?????"; * @@ -85,7 +90,6 @@ HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { * } * printf("New session state = %s\n", pszState); */ - return S_OK; } @@ -126,6 +130,20 @@ Session::Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx this->sessionControl = sessionControl; this->idx = idx; + AudioSessionState msState; + sessionControl->GetState(&msState); + switch (msState) { + case AudioSessionState::AudioSessionStateActive: + this->sessionState = SessionState::ACTIVE; + break; + case AudioSessionState::AudioSessionStateInactive: + this->sessionState = SessionState::INACTIVE; + break; + case AudioSessionState::AudioSessionStateExpired: + this->sessionState = SessionState::EXPIRED; + break; + } + sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); DWORD pid; sessionControl->GetProcessId(&pid); @@ -180,6 +198,10 @@ void Session::setVolume(NGuid guid, int channel, float volume) { } } +void Session::setIndex(size_t idx) { + this->idx = idx; +} + void Session::setMute(NGuid guid, bool muted) { GUID tempMsGuid = NGuidToGUID(guid); if(FAILED(sessionVolume->SetMute(muted, &tempMsGuid))) { log_wdebugcpp(std::wstring(L"SessionVolume null?")); }; @@ -268,6 +290,15 @@ wchar_t* fileDescription = NULL; return exePath; } +//todo: conflicting names. change callback name +void Session::setState(SessionState state) { + sessionState = state; +} + +SessionState Session::getState() { + return sessionState; +} + void Session::setStateCallback(SessionStateCallback *ssc){ sessionControl->RegisterAudioSessionNotification((IAudioSessionEvents*) ssc); } @@ -275,3 +306,8 @@ void Session::setStateCallback(SessionStateCallback *ssc){ void Session::removeStateCallback(SessionStateCallback *ssc){ sessionControl->UnregisterAudioSessionNotification((IAudioSessionEvents*) ssc); } + +Session::~Session() { + sessionControl->Release(); + sessionVolume->Release(); +} diff --git a/src/back/backsessionclasses.h b/src/back/backsessionclasses.h index 3354939..e6c8490 100644 --- a/src/back/backsessionclasses.h +++ b/src/back/backsessionclasses.h @@ -30,19 +30,25 @@ class Session { public: Session(Endpoint* ep, IAudioSessionControl2* sessionControl, size_t idx); + Session(Endpoint* ep, IAudioSessionControl2* sessionControl) : Session(ep, sessionControl, SIZE_MAX) {}; void setVolume(NGuid guid, int channel, float volume); float getVolume(int channel); void setMute(NGuid guid, bool muted); bool getMute(); + SessionState getState(); + void setState(SessionState state); + void setIndex(size_t idx); std::wstring getName(); void setStateCallback(SessionStateCallback *ssc); void removeStateCallback(SessionStateCallback *ssc); + ~Session(); //uint32_t getChannelCount(); private: std::wstring fetchProcessName(DWORD pid); std::wstring sessionName; + SessionState sessionState; Endpoint* ep; IAudioSessionControl2* sessionControl = nullptr; ISimpleAudioVolume* sessionVolume = nullptr; diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index a858b0f..7f7dabd 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -161,6 +161,19 @@ std::vector EndpointHandler::getSessionHandlers(){ return this->sessionHandlers; } +Endpoint* EndpointHandler::getEndpoint() { + return this->ep; +} + +void EndpointHandler::addSession(Session* session) { + ep->addSession(session); + + if (this->getState() == EndpointState::ENDPOINT_ACTIVE) { + SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); + sessionHandlers.push_back(sessionHandler); + } +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); epc->Release(); diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 4bdd519..e05f082 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -61,6 +61,11 @@ public: /* sessions */ size_t getSessionCount(); std::vector getSessionHandlers(); + void createNewSession(); + Endpoint* getEndpoint(); + + /*Session*/ + void addSession(Session* session); ~EndpointHandler(); private: diff --git a/src/global.h b/src/global.h index 1c72212..ced95ca 100644 --- a/src/global.h +++ b/src/global.h @@ -31,24 +31,31 @@ enum AudioChannel { }; enum EndpointState { - ENDPOINT_ACTIVE = (1 << 0), - ENDPOINT_DISABLED = (1 << 1), + ENDPOINT_ACTIVE = (1 << 0), + ENDPOINT_DISABLED = (1 << 1), ENDPOINT_NOTPRESENT = (1 << 2), - ENDPOINT_UNPLUGGED = (1 << 3), - ENDPOINT_ALL = 0x0F + ENDPOINT_UNPLUGGED = (1 << 3), + ENDPOINT_ALL = 0x0F +}; + +enum SessionState { + ACTIVE = (1 << 0), + INACTIVE = (1 << 1), + EXPIRED = (1 << 2), + ALL = 0x07 }; enum Flows { FLOW_PLAYBACK = (1 << 0), - FLOW_CAPTURE = (1 << 1), - FLOW_BOTH = (1 << 2), + FLOW_CAPTURE = (1 << 1), + FLOW_BOTH = (1 << 2), }; enum Roles { - ROLE_CONSOLE = (1 << 0), - ROLE_MULTIMEDIA = (1 << 1), + ROLE_CONSOLE = (1 << 0), + ROLE_MULTIMEDIA = (1 << 1), ROLE_COMMUNICATIONS = (1 << 2), - ROLE_ALL = 0x07, + ROLE_ALL = 0x07, }; struct NGuid { From 0dd016bb161f1fc5168f09a15710582cebe32f0c Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 3 Feb 2024 18:52:18 +0100 Subject: [PATCH 56/57] add session --- src/back/backlasses.cpp | 10 ++++++++- src/back/backlasses.h | 2 ++ src/cont/contclasses.cpp | 18 +++++++++------- src/cont/contclasses.h | 8 ++++--- src/qt/qtclasses.cpp | 45 ++++++++++++++++++++++++++++++++-------- src/qt/qtclasses.h | 24 ++++++++++++++------- 6 files changed, 79 insertions(+), 28 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index 677ce28..c1607cc 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -283,7 +283,7 @@ Endpoint::Endpoint(IMMDevice* ep, uint64_t idx){ friendlyName = std::wstring(pv.pwszVal); this->setFlow(); - if (this->endpointState == EndpointState::ENDPOINT_ACTIVE && this->flow == Flows::FLOW_PLAYBACK) { + if (this->flow == Flows::FLOW_PLAYBACK) { activateEndpointSessions(); } } @@ -538,6 +538,14 @@ size_t Endpoint::getSessionCount() { return endpointSessions.size(); } +void Endpoint::registerNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->RegisterSessionNotification(ensc); +} + +void Endpoint::unregisterNewSessionNotification(EndpointNewSessionCallback* ensc){ + sessionManager->UnregisterSessionNotification(ensc); +} + Endpoint::~Endpoint(){ log_wdebugcpp(L"murio endpoint-san uwu"); properties->Release(); diff --git a/src/back/backlasses.h b/src/back/backlasses.h index b95be81..f70e6d1 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -40,6 +40,8 @@ class Endpoint { std::vector getSessions(); size_t getSessionCount(); void addSession(Session* session); + void registerNewSessionNotification(EndpointNewSessionCallback* ensc); + void unregisterNewSessionNotification(EndpointNewSessionCallback* ensc); ~Endpoint(); diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 7f7dabd..1982bf8 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -9,13 +9,14 @@ EndpointHandler::EndpointHandler(uint64_t idx, Flows 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 && this->getState() == EndpointState::ENDPOINT_ACTIVE) { + 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); @@ -148,6 +149,10 @@ void EndpointHandler::removeRoles(Roles newRole){ ep->removeRoles(newRole); } +void EndpointHandler::setAddSessionWidgetFunction(std::function addSessionWidget) { + this->addSessionWidget = addSessionWidget; +} + /* sessions */ size_t EndpointHandler::getSessionCount() { return ep->getSessionCount(); @@ -168,14 +173,14 @@ Endpoint* EndpointHandler::getEndpoint() { void EndpointHandler::addSession(Session* session) { ep->addSession(session); - if (this->getState() == EndpointState::ENDPOINT_ACTIVE) { - SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); - sessionHandlers.push_back(sessionHandler); - } + SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); + sessionHandlers.push_back(sessionHandler); + this->addSessionWidget(sessionHandler); } EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); + ep->unregisterNewSessionNotification(ensc); epc->Release(); delete ep; } @@ -315,7 +320,6 @@ void OverseerHandler::setRemoveEndpointWidgetFunction(std::functionremoveEndpointWidget = removeEndpointWidget; } - void OverseerHandler::setEndpointHandlers(std::vector ephs){ this->playbackEndpointHandlers = ephs; } diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index e05f082..df4d755 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -10,6 +10,7 @@ class Endpoint; class EndpointVolumeCallback; class Overseer; class SessionHandler; +class EndpointNewSessionCallback; struct BackEndpointVolumeCallbackInfo { NGuid caller; @@ -66,7 +67,8 @@ public: /*Session*/ void addSession(Session* session); - + void setAddSessionWidgetFunction(std::function addSessionWidget); + ~EndpointHandler(); private: std::vector getSessions(); @@ -81,7 +83,9 @@ private: uint64_t frontIdx = INT_MAX; }; EndpointHandlerFrontVisibility ephfv; + EndpointNewSessionCallback* ensc; std::vector sessionHandlers; + std::function addSessionWidget; //QSlider *slidy; }; @@ -109,7 +113,6 @@ public: EndpointHandler* addEndpoint(std::wstring endpointId, Flows *flow); NGuid getGuid(); - /* Session's */ /* * void setSessionVolumeCallback(std::function changeSessionVolume); * void setSessionVolume(float newValue, ); @@ -125,7 +128,6 @@ private: /* Session's */ std::function changeSessionVolume; - //std::function updateFrontVolumeCallback; //std::function updateFrontMuteCallback; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index f420688..96cbcc3 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -1,7 +1,7 @@ #include "qtclasses.h" template -EndpointWidgetEvent::EndpointWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ +CustomWidgetEvent::CustomWidgetEvent(QEvent::Type type, T payload) : QEvent(type){ this->payload = payload; } @@ -97,7 +97,7 @@ void SessionWidget::updateMainVolume(int newValue){ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ //todo: based on qgridlayout, name+mute should be its own widget, same with channels - int row = 0; + row = 0; this->idx = idx; this->eph = eph; //todo: sussy @@ -255,13 +255,18 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare }); timer->start(10); - /* sessions */ + /* First SessionWidget batch */ for (size_t i = 0; i < eph->getSessionCount(); i++) { SessionWidget* sessionWidget = new SessionWidget(i, eph->getSessionHandlers().at(i), this); layout->addWidget(sessionWidget, row, 4); row++; sessionWidgets.push_back(sessionWidget); } + + /* New SessionWidget callback */ + eph->setAddSessionWidgetFunction([this](SessionHandler* sessionHandler) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetCreated, sessionHandler)); + }); //todo parent? layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); @@ -271,6 +276,28 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare log_debugcpp("ENDPOINT_WIDGETED"); } +void EndpointWidget::addSessionWidget(CustomWidgetEvent* ev){ + SessionWidget* sw = new SessionWidget(this->sessionWidgets.size(), ev->payload, this); + this->layout->addWidget(sw, row, 4); + row++; + sessionWidgets.push_back(sw); + return; +} + +void EndpointWidget::customEvent(QEvent* ev) { + if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetCreated) { + ev->setAccepted(true); + this->addSessionWidget((CustomWidgetEvent*) ev); + return; + } else if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetObsolete) { + ev->setAccepted(true); + //this->addEndpointWidget((CustomWidgetEvent*)ev); + } + // Make sure the rest of events are handled + QWidget::customEvent(ev); +} + + EndpointWidget::~EndpointWidget() { timer->stop(); delete timer; @@ -280,17 +307,17 @@ EndpointWidget::~EndpointWidget() { void MainWindow::customEvent(QEvent* ev) { if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { ev->setAccepted(true); - this->removeEndpointWidget((EndpointWidgetEvent*)ev); + this->removeEndpointWidget((CustomWidgetEvent*)ev); return; } else if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetCreated) { ev->setAccepted(true); - this->addEndpointWidget((EndpointWidgetEvent*)ev); + this->addEndpointWidget((CustomWidgetEvent*)ev); } // Make sure the rest of events are handled QMainWindow::customEvent(ev); } -void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ +void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->layout->removeWidget(ews.at(i)); @@ -311,7 +338,7 @@ void MainWindow::removeEndpointWidget(EndpointWidgetEvent* ev){ return; } -void MainWindow::addEndpointWidget(EndpointWidgetEvent* ev){ +void MainWindow::addEndpointWidget(CustomWidgetEvent* ev){ EndpointWidget* epw = new EndpointWidget(this->ews.size(), ev->payload, widget); this->layout->addWidget(epw); ews.push_back(epw); @@ -497,11 +524,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { }); osh->setRemoveEndpointWidgetFunction([this](uint64_t index) { - QCoreApplication::instance()->postEvent(this, new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index)); + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetObsolete, index)); }); osh->setAddEndpointWidgetFunction([this](EndpointHandler* eph) { - QCoreApplication::instance()->postEvent(this, new EndpointWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetCreated, eph)); + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::EndpointWidgetCreated, eph)); }); } diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 6cf85b0..c130fdd 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -58,15 +58,17 @@ */ enum CustomQEvent { EndpointWidgetObsolete = 1001, - EndpointWidgetCreated = 1002, - EndpointDefaultChange = 1003, + EndpointWidgetCreated = 1002, + EndpointDefaultChange = 1003, + SessionWidgetCreated = 1004, + SessionWidgetObsolete = 1005 }; template -class EndpointWidgetEvent : public QEvent { +class CustomWidgetEvent : public QEvent { public: - EndpointWidgetEvent(QEvent::Type type, T payload); + CustomWidgetEvent(QEvent::Type type, T payload); T payload; }; @@ -130,7 +132,14 @@ public slots: void updateMainVolume(int newValue); void updateMute(int checked); +protected: + void customEvent(QEvent* ev) override; + +private slots: + void addSessionWidget(CustomWidgetEvent* ev); + private: + int row; QCheckBox *muteButton = nullptr; QLabel *mainLabel = nullptr, *leftChannelLabel = nullptr, *rightChannelLabel = nullptr; QSlider *mainSlider = nullptr; @@ -138,8 +147,7 @@ private: std::vector channelLabels; QGridLayout *layout = nullptr; QGridLayout *mainMuteLayout = nullptr; - std::map defaultRolesCheckBoxes; - + std::map defaultRolesCheckBoxes; EndpointHandler* eph; size_t defaultRolesVectorSize = 4; @@ -168,8 +176,8 @@ protected: private slots: void trayIconActivated(QSystemTrayIcon::ActivationReason reason); - void removeEndpointWidget(EndpointWidgetEvent* ev); - void addEndpointWidget(EndpointWidgetEvent* ev); + void removeEndpointWidget(CustomWidgetEvent* ev); + void addEndpointWidget(CustomWidgetEvent* ev); void reorderEndpointWidgetCollection(); //TODO: destroy/empty existing EndpointWidgets //void setEndpointHandlers(std::vector *ephs); From 5211b7066980afc5b4ea58cd82a9b11b1e613544 Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 3 Feb 2024 20:32:49 +0100 Subject: [PATCH 57/57] sessions dynamically removed --- src/back/backlasses.cpp | 3 +- src/back/backsessionclasses.cpp | 63 +++++++++------------------------ src/cont/contclasses.cpp | 14 +++++++- src/cont/contclasses.h | 6 +++- src/cont/contsessionclasses.cpp | 38 ++++++++++++++++++++ src/cont/contsessionclasses.h | 7 +++- src/global.h | 9 ++--- src/qt/qtclasses.cpp | 43 +++++++++++++--------- src/qt/qtclasses.h | 4 ++- 9 files changed, 114 insertions(+), 73 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index c1607cc..8960c49 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -47,7 +47,7 @@ HRESULT EndpointNewSessionCallback::OnSessionCreated(IAudioSessionControl *NewSe sessionControl->AddRef(); //sessionControl->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&sessionVolume); Session* newSession = new Session(this->eph->getEndpoint(), sessionControl); - eph->addSession(newSession); + eph->addSessionSendFront(newSession); } if (result == S_OK) @@ -602,7 +602,6 @@ void Overseer::reloadEndpoints(Flows flow) { if(deviceCollection->Item(i, &temp) != 0) { log_debugcpp("si"); }; Endpoint *endpoint = new Endpoint(temp, i); - //endpoint->setIndex(i); if (flow == Flows::FLOW_PLAYBACK) this->playbackDevices.push_back(endpoint); else diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp index 7f3b101..ced7fb1 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -71,57 +71,26 @@ HRESULT SessionStateCallback::OnGroupingParamChanged(LPCGUID NewGroupingParam, L } HRESULT SessionStateCallback::OnStateChanged(AudioSessionState NewState) { - /* enum _AudioSessionState { - * AudioSessionStateInactive, - * AudioSessionStateActive, - * AudioSessionStateExpired - * } AudioSessionState; */ - /* - * char *pszState = "?????"; - * - * switch (NewState) - * { - * case AudioSessionStateActive: - * pszState = "active"; - * break; - * case AudioSessionStateInactive: - * pszState = "inactive"; - * break; - * } - * printf("New session state = %s\n", pszState); - */ + SessionState newState;// = sh->getState(); + switch (NewState) { + case AudioSessionStateActive: + newState = SessionState::ACTIVE; + break; + case AudioSessionStateInactive: + newState = SessionState::INACTIVE; + break; + case AudioSessionStateExpired: + newState = SessionState::EXPIRED; + break; + } + + sh->reviseSessionShowing(newState); return S_OK; } HRESULT SessionStateCallback::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) { - /* - * char *pszReason = "?????"; - * - * switch (DisconnectReason) - * { - * case DisconnectReasonDeviceRemoval: - * pszReason = "device removed"; - * break; - * case DisconnectReasonServerShutdown: - * pszReason = "server shut down"; - * break; - * case DisconnectReasonFormatChanged: - * pszReason = "format changed"; - * break; - * case DisconnectReasonSessionLogoff: - * pszReason = "user logged off"; - * break; - * case DisconnectReasonSessionDisconnected: - * pszReason = "session disconnected"; - * break; - * case DisconnectReasonExclusiveModeOverride: - * pszReason = "exclusive-mode override"; - * break; - * } - * printf("Audio session disconnected (reason: %s)\n", - * pszReason); - */ - + sh->setState(SessionState::DISCONNECTED); + sh->reviseSessionShowing(SessionState::DISCONNECTED); return S_OK; } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 1982bf8..38c30b1 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -153,6 +153,10 @@ void EndpointHandler::setAddSessionWidgetFunction(std::functionaddSessionWidget = addSessionWidget; } +void EndpointHandler::setRemoveSessionWidgetFunction(std::function removeSessionWidget) { + this->removeSessionWidget = removeSessionWidget; +} + /* sessions */ size_t EndpointHandler::getSessionCount() { return ep->getSessionCount(); @@ -170,7 +174,7 @@ Endpoint* EndpointHandler::getEndpoint() { return this->ep; } -void EndpointHandler::addSession(Session* session) { +void EndpointHandler::addSessionSendFront(Session* session) { ep->addSession(session); SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); @@ -178,6 +182,14 @@ void EndpointHandler::addSession(Session* session) { this->addSessionWidget(sessionHandler); } +void EndpointHandler::sendSessionToFront(SessionHandler* sh) { + this->addSessionWidget(sh); +} + +void EndpointHandler::removeSessionFromFront(SessionHandler* sh) { + this->removeSessionWidget(sh); +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); ep->unregisterNewSessionNotification(ensc); diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index df4d755..25fcaea 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -66,8 +66,11 @@ public: Endpoint* getEndpoint(); /*Session*/ - void addSession(Session* session); + void addSessionSendFront(Session* session); void setAddSessionWidgetFunction(std::function addSessionWidget); + void setRemoveSessionWidgetFunction(std::function removeSessionWidget); + void sendSessionToFront(SessionHandler* sh); + void removeSessionFromFront(SessionHandler* sh); ~EndpointHandler(); private: @@ -86,6 +89,7 @@ private: EndpointNewSessionCallback* ensc; std::vector sessionHandlers; std::function addSessionWidget; + std::function removeSessionWidget; //QSlider *slidy; }; diff --git a/src/cont/contsessionclasses.cpp b/src/cont/contsessionclasses.cpp index e3f69d0..155c0e1 100644 --- a/src/cont/contsessionclasses.cpp +++ b/src/cont/contsessionclasses.cpp @@ -36,6 +36,44 @@ bool SessionHandler::getMute(){ return session->getMute(); } +void SessionHandler::setFrontIndex(uint64_t frontIdx) { + this->frontIdx = frontIdx; +} + +uint64_t SessionHandler::getFrontIndex() { + return frontIdx; +} + SessionVolumeInfo* SessionHandler::getVolumeInfo() { return &svi; } + +SessionState SessionHandler::getState() { + return session->getState(); +} + +void SessionHandler::setState(SessionState state) { + session->setState(state); +} + +void SessionHandler::reviseSessionShowing(SessionState state) { + SessionState currentState = this->getState(); + switch (currentState) { + case SessionState::ACTIVE: + case SessionState::INACTIVE: + if (state == SessionState::EXPIRED) { + eph->removeSessionFromFront(this); + } + break; + case SessionState::EXPIRED: + if (state == SessionState::ACTIVE || INACTIVE) { + eph->sendSessionToFront(this); + } + break; + case SessionState::DISCONNECTED: + if (frontIdx != INT_MAX) + eph->removeSessionFromFront(this); + break; + } +} + diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h index e637e47..51f620f 100644 --- a/src/cont/contsessionclasses.h +++ b/src/cont/contsessionclasses.h @@ -24,7 +24,12 @@ class SessionHandler { float getVolume(int channel); void setMute(NGuid guid, bool muted); bool getMute(); + void setFrontIndex(uint64_t frontIdx); + SessionState getState(); + void setState(SessionState state); + uint64_t getFrontIndex(); std::wstring getName(); + void reviseSessionShowing(SessionState state); SessionVolumeInfo* getVolumeInfo(); private: @@ -33,5 +38,5 @@ class SessionHandler { Session* session; SessionStateCallback* ssc; size_t idx; - + uint64_t frontIdx = INT_MAX; }; diff --git a/src/global.h b/src/global.h index ced95ca..292ec06 100644 --- a/src/global.h +++ b/src/global.h @@ -39,10 +39,11 @@ enum EndpointState { }; enum SessionState { - ACTIVE = (1 << 0), - INACTIVE = (1 << 1), - EXPIRED = (1 << 2), - ALL = 0x07 + ACTIVE = (1 << 0), + INACTIVE = (1 << 1), + EXPIRED = (1 << 2), + DISCONNECTED = (1 << 3), + ALL = 0x0F }; enum Flows { diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 96cbcc3..a9da261 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -95,6 +95,11 @@ void SessionWidget::updateMainVolume(int newValue){ this->sh->setVolume(osh->getGuid(), AudioChannel::CHANNEL_MAIN, newValue); } +SessionWidget::~SessionWidget() { + volumePoller->stop(); + delete volumePoller; +} + EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *parent) : QWidget(parent){ //todo: based on qgridlayout, name+mute should be its own widget, same with channels row = 0; @@ -261,12 +266,17 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare layout->addWidget(sessionWidget, row, 4); row++; sessionWidgets.push_back(sessionWidget); + eph->getSessionHandlers().at(i)->setFrontIndex(i); } - /* New SessionWidget callback */ + /* Add/Remove SessionWidget callback */ eph->setAddSessionWidgetFunction([this](SessionHandler* sessionHandler) { QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetCreated, sessionHandler)); }); + + eph->setRemoveSessionWidgetFunction([this](SessionHandler* sessionHandler) { + QCoreApplication::instance()->postEvent(this, new CustomWidgetEvent((QEvent::Type)CustomQEvent::SessionWidgetObsolete, sessionHandler)); + }); //todo parent? layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Minimum), 1, 0); @@ -277,23 +287,34 @@ EndpointWidget::EndpointWidget(uint64_t idx, EndpointHandler* eph, QWidget *pare } void EndpointWidget::addSessionWidget(CustomWidgetEvent* ev){ - SessionWidget* sw = new SessionWidget(this->sessionWidgets.size(), ev->payload, this); + uint64_t index = this->sessionWidgets.size(); + SessionWidget* sw = new SessionWidget(index, ev->payload, this); + ev->payload->setFrontIndex(index); this->layout->addWidget(sw, row, 4); row++; sessionWidgets.push_back(sw); return; } +void EndpointWidget::removeSessionWidget(CustomWidgetEvent* ev){ + uint64_t i = ev->payload->getFrontIndex(); + this->sessionWidgets.at(i)->setParent(nullptr); + this->layout->removeWidget(sessionWidgets.at(i)); + delete sessionWidgets.at(i); + sessionWidgets.at(i) = nullptr; + ev->payload->setFrontIndex(INT_MAX); + //this->sessionWidgetsUpdateTimer->start(); + return; +} + void EndpointWidget::customEvent(QEvent* ev) { if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetCreated) { ev->setAccepted(true); this->addSessionWidget((CustomWidgetEvent*) ev); - return; } else if (ev->type() == (QEvent::Type)CustomQEvent::SessionWidgetObsolete) { ev->setAccepted(true); - //this->addEndpointWidget((CustomWidgetEvent*)ev); + this->removeSessionWidget((CustomWidgetEvent*) ev); } - // Make sure the rest of events are handled QWidget::customEvent(ev); } @@ -308,12 +329,10 @@ void MainWindow::customEvent(QEvent* ev) { if (ev->type() == CustomQEvent::EndpointWidgetObsolete) { ev->setAccepted(true); this->removeEndpointWidget((CustomWidgetEvent*)ev); - return; } else if (ev->type() == (QEvent::Type)CustomQEvent::EndpointWidgetCreated) { ev->setAccepted(true); this->addEndpointWidget((CustomWidgetEvent*)ev); } - // Make sure the rest of events are handled QMainWindow::customEvent(ev); } @@ -326,15 +345,6 @@ void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ delete ews.at(i); ews.at(i) = nullptr; this->ewsUpdateTimer->start(); - - /* - * while ((i + 1) < ews.size()) { - * ews.at(i) = ews.at(i + 1); - * ews.at(i)->setIndex(i); - * i++; - * } - * ews.pop_back(); - */ return; } @@ -346,6 +356,7 @@ void MainWindow::addEndpointWidget(CustomWidgetEvent* ev){ } void MainWindow::reorderEndpointWidgetCollection() { + /* Flatten */ size_t firstNullPosition = 0; size_t ewsSize = ews.size(); bool breakSorting = false; diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index c130fdd..b9c4fa8 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -92,7 +92,8 @@ Q_OBJECT public: SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent /* = nullptr */); - + ~SessionWidget(); + public slots: void updateMainVolume(int newValue); void updateMute(int checked); @@ -137,6 +138,7 @@ protected: private slots: void addSessionWidget(CustomWidgetEvent* ev); + void removeSessionWidget(CustomWidgetEvent* ev); private: int row;