From 3239e60471f71aca363293e73e74d26a1d311dfe Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 25 Jan 2025 17:27:36 +0100 Subject: [PATCH 1/2] fixed synch issues + memleak @ name fetching --- src/back/backlasses.cpp | 51 +++++++++++++++++++++++---------- src/back/backlasses.h | 15 +++++----- src/back/backsessionclasses.cpp | 15 ++++++---- src/cont/contclasses.cpp | 24 +++++++++++++--- src/cont/contclasses.h | 2 ++ src/cont/contsessionclasses.h | 8 +++--- src/qt/qtclasses.cpp | 15 +++++----- 7 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/back/backlasses.cpp b/src/back/backlasses.cpp index cead61f..74af6a0 100644 --- a/src/back/backlasses.cpp +++ b/src/back/backlasses.cpp @@ -234,6 +234,7 @@ HRESULT EndpointSituationCallback::OnDeviceRemoved(LPCWSTR pwstrDeviceId) { HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) { std::wstring endpointId = std::wstring(pwstrDeviceId); + log_wdebugcpp(L"Endpoint state change for " + endpointId); EndpointState newState; switch (dwNewState) { case DEVICE_STATE_ACTIVE: @@ -249,7 +250,7 @@ HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, D newState = EndpointState::ENDPOINT_UNPLUGGED; break; } - isEpStateChanging = true; + isEpStateChanging.exchange(true); std::thread newEndpointThread(&OverseerHandler::reviseEndpointShowing, osh, endpointId, newState); newEndpointThread.detach(); @@ -258,7 +259,7 @@ HRESULT EndpointSituationCallback::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, D } HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) { - isEpStateChanging = true; + isEpStateChanging.exchange(true); std::thread propertyThread(&Overseer::updateEndpointInfo, os, std::wstring(pwstrDeviceId)); propertyThread.detach(); while(isEpStateChanging); @@ -266,7 +267,8 @@ HRESULT EndpointSituationCallback::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, } void EndpointSituationCallback::reportFinishedStateChange() { - this->isEpStateChanging = false; + this->isEpStateChanging.exchange(false); + return; } Endpoint::Endpoint(IMMDevice* ep, IPolicyConfig7* policyConfig, uint64_t idx) { @@ -325,23 +327,29 @@ void Endpoint::activateEndpointSessions() { return; } - if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), - CLSCTX_ALL, NULL, (void**) &sessionManager))) { - log_debugcpp("Couldn't open session manager2, huh"); - return; + if (!sessionManager) { + if (FAILED(endpoint->Activate(__uuidof(IAudioSessionManager2), + CLSCTX_ALL, NULL, (void**) &sessionManager))) { + log_debugcpp("Couldn't open session manager2, huh"); + return; + } } + IAudioSessionEnumerator* sessionEnumerator = nullptr; - if (FAILED(sessionManager->GetSessionEnumerator(&sessionEnumerator))) { log_wdebugcpp(L"sesEnumeratorBros..."); return; } + if (FAILED(sessionManager->GetSessionEnumerator(&sessionEnumerator))) { log_wdebugcpp(L"sesEnumeratorBros..."); exit(-5); return; } endpointSessions.resize(1, nullptr); int sessionCount; sessionEnumerator->GetCount(&sessionCount); for (int i = 0; i < sessionCount; i++) { IAudioSessionControl* sessionControlTmp; - sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp); + if (FAILED(sessionEnumerator->GetSession(i, (IAudioSessionControl**)&sessionControlTmp))) { + exit(-6); + } IAudioSessionControl2* sessionControl; - sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl); - sessionControl->AddRef(); + if(FAILED(sessionControlTmp->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&sessionControl))){ + exit(-7); + } sessionControlTmp->Release(); Session* session = new Session(this, sessionControl, (size_t)i); if (sessionControl->IsSystemSoundsSession() == S_OK) endpointSessions[0] = session; @@ -350,6 +358,13 @@ void Endpoint::activateEndpointSessions() { sessionEnumerator->Release(); } +/* + * void Endpoint::deleteSessionManager() { + * sessionManager->Release(); + * sessionManager = nullptr; + * } + */ + void Endpoint::addSession(Session* session) { session->setIndex(this->getSessionCount()); endpointSessions.push_back(session); @@ -357,9 +372,15 @@ void Endpoint::addSession(Session* session) { void Endpoint::activateEndpointVolume() { //If this EP is created after init, COM won't be initialized on the executing thread. - HRESULT result = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + HRESULT result = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); if (this->endpointVolume == nullptr) { - endpoint->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&this->endpointVolume); + if(FAILED(endpoint->Activate( + IID_IAudioEndpointVolume, + CLSCTX_ALL, + NULL, + (void**)&this->endpointVolume))) { + log_debugcpp("No volume, huh"); + } //todo: check header if(FAILED(endpoint->Activate(__uuidof(IAudioMeterInformation), @@ -594,8 +615,8 @@ void Overseer::createEndpoints(Flows flow) { /* * Counting them */ - if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; - if(numEndpoints == 0) { log_debugcpp("si"); }; + if(FAILED(deviceCollection->GetCount(&numEndpoints))) { log_debugcpp("si");}; + if(numEndpoints == 0) { log_debugcpp("si"); }; /* * Retrieving actual endpoints and storing them on their own collection diff --git a/src/back/backlasses.h b/src/back/backlasses.h index 9f79924..5acedba 100644 --- a/src/back/backlasses.h +++ b/src/back/backlasses.h @@ -82,6 +82,7 @@ class Endpoint { void unregisterNewSessionNotification(EndpointNewSessionCallback* ensc); void deleteSessions(); void activateEndpointSessions(); + //void deleteSessionManager(); std::mutex endpointSessionsMutex; ~Endpoint(); @@ -91,12 +92,13 @@ class Endpoint { std::vector endpointSessions; uint32_t channelCount = 0; IMMDevice *endpoint; - IAudioClient *audioClient; + IAudioEndpointVolume *endpointVolume = nullptr; + IPropertyStore *properties; + IAudioMeterInformation *endpointPeakMeter = nullptr; + //IAudioClient *audioClient; int64_t defTime, minTime; IAudioSessionManager2 *sessionManager = nullptr; Flows flow; - IAudioEndpointVolume *endpointVolume = nullptr; - IPropertyStore *properties; std::wstring friendlyName; std::wstring descriptionName; std::wstring deviceName; @@ -105,7 +107,6 @@ class Endpoint { Roles endpointRoles = (Roles)0; uint64_t idx; //Not implemented in llvm-mingw. Sad! todo: mingw patch - IAudioMeterInformation *endpointPeakMeter = nullptr; IPolicyConfig7* policyConfig; }; @@ -125,7 +126,7 @@ class EndpointVolumeCallback : public IAudioEndpointVolumeCallback { private: ULONG ref = 1; Endpoint* ep; - bool wait = false; + std::atomic wait = false; }; class EndpointSituationCallback : public IMMNotificationClient { @@ -143,7 +144,7 @@ class EndpointSituationCallback : public IMMNotificationClient { private: ULONG ref = 1; Overseer* os; - bool isEpStateChanging = false; + std::atomic isEpStateChanging = false; }; class Overseer { @@ -204,7 +205,7 @@ class EndpointNewSessionCallback : public IAudioSessionNotification { void createSessionThread(SessionThreadParams params); private: - bool wait = false; + std::atomic wait = false; ULONG ref = 1; EndpointHandler *eph; diff --git a/src/back/backsessionclasses.cpp b/src/back/backsessionclasses.cpp index 5816ac5..c9aaa85 100644 --- a/src/back/backsessionclasses.cpp +++ b/src/back/backsessionclasses.cpp @@ -39,6 +39,7 @@ HRESULT SessionStateCallback::QueryInterface(REFIID riid, VOID **ppvInterface) { HRESULT SessionStateCallback::OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) { //TODO: Preguntar while(sh->getVolumeInfo()->isNameChanged == true); + sh->setName(std::wstring(NewDisplayName)); sh->getVolumeInfo()->isNameChanged = true; return S_OK; @@ -238,13 +239,15 @@ bool Session::fetchNameViaFD(std::wstring exePath, DWORD pid, std::wstring *sess void* fileVersionInfo = malloc(fileVersionInfoSize); if(!GetFileVersionInfoExW(FILE_VER_GET_LOCALISED | FILE_VER_GET_NEUTRAL, - exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) + exePath.c_str(),0,fileVersionInfoSize, fileVersionInfo)) { return false; + } UINT translationArrayLen = 0; - if (!VerQueryValueW(fileVersionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&translationArray, &translationArrayLen)) + if (!VerQueryValueW(fileVersionInfo, L"\\VarFileInfo\\Translation", (LPVOID*)&translationArray, &translationArrayLen)) { + free(fileVersionInfo); return false; - + } //File descriptor parsing //TODO: https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserpreferreduilanguages /* It is possible to retrieve user languages and try to use one of those before falling back to whatever @@ -257,7 +260,6 @@ bool Session::fetchNameViaFD(std::wstring exePath, DWORD pid, std::wstring *sess int8_t syslangIdx = -1; wchar_t metadataStringKey[256]; wchar_t* metadataString = NULL; - //std::wstring name; for (UINT i = 0; i < availableLangs; i++) { LANGID defaultUILanguage = GetUserDefaultUILanguage(); if (defaultUILanguage != translationArray[i].wLanguage) @@ -340,8 +342,6 @@ bool Session::fetchNameViaMSIX(std::wstring exePath, DWORD pid, std::wstring *se if (sessionName->length() > 0) return true; else return false; - - } @@ -419,4 +419,7 @@ Session::~Session() { meterInformation->Release(); sessionControl->Release(); sessionVolume->Release(); + meterInformation = nullptr; + sessionControl = nullptr; + sessionVolume = nullptr; } diff --git a/src/cont/contclasses.cpp b/src/cont/contclasses.cpp index 27ae4a6..d2188f0 100644 --- a/src/cont/contclasses.cpp +++ b/src/cont/contclasses.cpp @@ -28,7 +28,6 @@ EndpointHandler::EndpointHandler(uint64_t idx, Flows flow) { 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()); @@ -115,7 +114,10 @@ void EndpointHandler::setBackEndpointVolumeCallbackInfoContent(uint8_t state) { callbackInfo.muted = this->getMute(); callbackInfo.mainVolume = this->getVolume(AudioChannel::CHANNEL_MAIN); callbackInfo.channels = this->getChannelCount(); - ep->setVolumeCallback(epc); + if (!epc) { + epc = new EndpointVolumeCallback(ep); + ep->setVolumeCallback(epc); + } callbackInfo.channelVolumes.resize(this->callbackInfo.channels); for(uint32_t i = 0; i < this->getChannelCount(); i++){ callbackInfo.channelVolumes.at(i) = this->getVolume(i); @@ -181,13 +183,13 @@ Endpoint* EndpointHandler::getEndpoint() { void EndpointHandler::addSessionSendFront(Session* session) { if(!ep->endpointSessionsMutex.try_lock()) return; + sessionHandlersMutex.lock(); this->ep->addSession(session); SessionHandler* sessionHandler = new SessionHandler(this, session, (getSessionCount() - 1)); - ep->endpointSessionsMutex.unlock(); - sessionHandlersMutex.lock(); sessionHandlers.push_back(sessionHandler); + ep->endpointSessionsMutex.unlock(); sessionHandlersMutex.unlock(); this->addSessionWidget(sessionHandler); } @@ -203,11 +205,13 @@ void EndpointHandler::removeSessionFromFront(SessionHandler* sh) { void EndpointHandler::deleteSessions() { ep->unregisterNewSessionNotification(ensc); ensc->Release(); + ensc = nullptr; for (auto sh : sessionHandlers) { delete sh; } sessionHandlers.resize(0); ep->deleteSessions(); + //ep->deleteSessionManager(); } void EndpointHandler::createSessionHandlers() { @@ -218,6 +222,9 @@ void EndpointHandler::createSessionHandlers() { sessionHandlers.push_back(sessionHandler); } } +} + +void EndpointHandler::createSessionHandlersCallback() { ensc = new EndpointNewSessionCallback(this); ep->registerNewSessionNotification(ensc); } @@ -232,6 +239,12 @@ void EndpointHandler::unlockSessionCollections() { this->ep->endpointSessionsMutex.unlock(); } +void EndpointHandler::removeVolumeCallback() { + ep->removeVolumeCallback(epc); + epc->Release(); + epc = nullptr; +} + EndpointHandler::~EndpointHandler() { ep->removeVolumeCallback(epc); ep->unregisterNewSessionNotification(ensc); @@ -411,10 +424,13 @@ void OverseerHandler::reviseEndpointShowing(std::wstring endpointId, EndpointSta if (flow == Flows::FLOW_CAPTURE) goto end; if (eph && EndpointState::ENDPOINT_ACTIVE & state) { + eph->setState(EndpointState::ENDPOINT_ACTIVE); this->addEndpointWidget(eph); } else if (eph && eph->getFrontVisibilityState() == EndpointState::ENDPOINT_ACTIVE) { + eph->removeVolumeCallback(); this->removeEndpointWidget(eph->getFrontVisibilityIndex()); } + end: handlersPlaybackMutex.unlock(); handlersCaptureMutex.unlock(); diff --git a/src/cont/contclasses.h b/src/cont/contclasses.h index 2034986..cd0e03e 100644 --- a/src/cont/contclasses.h +++ b/src/cont/contclasses.h @@ -76,9 +76,11 @@ public: void removeSessionFromFront(SessionHandler* sh); void deleteSessions(); void createSessionHandlers(); + void createSessionHandlersCallback(); std::mutex sessionHandlersMutex; void lockSessionCollections(); void unlockSessionCollections(); + void removeVolumeCallback(); ~EndpointHandler(); private: diff --git a/src/cont/contsessionclasses.h b/src/cont/contsessionclasses.h index 455bbe1..24c0ba7 100644 --- a/src/cont/contsessionclasses.h +++ b/src/cont/contsessionclasses.h @@ -9,10 +9,10 @@ class SessionStateCallback; struct SessionVolumeInfo { //SessionVolumeInfo(bool muted, float mainVolume); - bool muted; - float mainVolume; - NGuid caller; - bool isNameChanged = false; + bool muted; + float mainVolume; + NGuid caller; + std::atomic isNameChanged = false; //size_t channels; //std::vector channelVolumes; }; diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index 2e2a4b0..a5c391d 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -411,7 +411,8 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) if (sh->getVolumeInfo()->isNameChanged) { mainLabel->setText(QString::fromStdWString(sh->getName())); mainLabel->setToolTip(QString::fromStdWString(sh->getName())); - } + sh->getVolumeInfo()->isNameChanged = false; + } const float roundingFactor = 0.005; mainSlider->blockSignals(true); muteButton->blockSignals(true); @@ -538,7 +539,7 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i eph->createSessionHandlers(); //todo: sussy - this->eph->setState(EndpointState::ENDPOINT_ACTIVE, idx); + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, idx); this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); widgetLayout = new QGridLayout(this); //this->setContentsMargins(0, 0, 0, 0); @@ -698,9 +699,9 @@ EndpointWidget::EndpointWidget(EndpointHandler* eph, QWidget *parent, uint64_t i }); log_debugcpp("ENDPOINT_WIDGETED"); + eph->createSessionHandlersCallback(); } - void EndpointWidget::addSessionWidget(CustomWidgetEvent* ev){ this->setUpdatesEnabled(false); uint64_t index = this->sessionWidgets.size(); @@ -779,7 +780,7 @@ void MainWindow::customEvent(QEvent* ev) { } //__attribute__((optimize("O0", "unroll-loops"))) -void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ +void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev) { uint64_t i = ev->payload; this->ews.at(i)->setParent(nullptr); this->widgetLayout->removeWidget(ews.at(i)); @@ -792,7 +793,7 @@ void MainWindow::removeEndpointWidget(CustomWidgetEvent* ev){ return; } -void MainWindow::addEndpointWidget(CustomWidgetEvent* ev){ +void MainWindow::addEndpointWidget(CustomWidgetEvent* ev) { EndpointWidget* epw = new EndpointWidget(ev->payload, containerWidget, this->ews.size()); //epw->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); epw->setParent(this); @@ -924,9 +925,9 @@ EndpointHandler* EndpointWidget::getEndpointHandler(){ * } */ -void EndpointWidget::setIndex(uint64_t idx){ +void EndpointWidget::setIndex(uint64_t idx) { this->idx = idx; - this->eph->setState(EndpointState::ENDPOINT_ACTIVE, this->idx); + this->eph->setFrontVisibilityInfo(EndpointState::ENDPOINT_ACTIVE, this->idx); } uint64_t EndpointWidget::getIndex(){ From 8f864f33941dcfcb835a97771f5e7f803cf0141b Mon Sep 17 00:00:00 2001 From: Hane Date: Sat, 31 Jan 2026 18:09:02 +0100 Subject: [PATCH 2/2] wip: icons --- assets.qrc | 2 ++ bueno.bat | 6 +++--- qtest.pro | 2 +- src/qt/qtclasses.cpp | 38 ++++++++++++++++++++++++++++++--- src/qt/qtclasses.h | 7 +++++- src/qt/qtcommon.h | 32 +++++++++++++++++++++++++++ src/qt/qtvisuals.h | 51 ++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 128 insertions(+), 10 deletions(-) diff --git a/assets.qrc b/assets.qrc index 4ef5956..2206a8a 100644 --- a/assets.qrc +++ b/assets.qrc @@ -4,5 +4,7 @@ assets/notificationAreaIcon.png assets/style.qss assets/logo.ico + assets/mute.svg + assets/unmute.svg diff --git a/bueno.bat b/bueno.bat index 37b5dec..540be60 100644 --- a/bueno.bat +++ b/bueno.bat @@ -1,7 +1,7 @@ taskkill /F /IM "MixerQ.exe" taskkill /F /IM "MixerQd.exe" qmake -o build\Makefile .\qtest.pro -mingw32-make.exe -C .\build -f Makefile.Release +REM mingw32-make.exe -C .\build -f Makefile.Release mingw32-make.exe -C .\build -f Makefile.Debug -makensis /DBUILDTYPE=release install\installer.nsi -makensis /DBUILDTYPE=debug install\installer.nsi +REM makensis /DBUILDTYPE=release install\installer.nsi +REM makensis /DBUILDTYPE=debug install\installer.nsi diff --git a/qtest.pro b/qtest.pro index 0568bca..42a1165 100644 --- a/qtest.pro +++ b/qtest.pro @@ -18,7 +18,7 @@ LIBS += -lWinmm -lodbc32 -lodbccp32 -luuid -loleaut32 -lole32 -lshell32 -ladvapi DEFINES += QT_LOGGING_TO_CONSOLE=1 WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0602 DEFINES_DEBUG += DEBUG -QT += widgets network +QT += widgets network svg INCLUDEPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\back\reimpl" "$$PWD\src\cont" VPATH += "$$PWD\src" "$$PWD\src\qt" "$$PWD\src\back" "$$PWD\src\back\reimpl" "$$PWD\src\cont" diff --git a/src/qt/qtclasses.cpp b/src/qt/qtclasses.cpp index a5c391d..a9148b9 100644 --- a/src/qt/qtclasses.cpp +++ b/src/qt/qtclasses.cpp @@ -207,6 +207,36 @@ void ExtendedCheckBox::customEvent(QEvent* ev) { QCheckBox::customEvent(ev); } +void ExtendedCheckBox::paintEvent(QPaintEvent *event) { + QStylePainter p(this); + QStyleOptionButton opt; + initStyleOption(&opt); + opt.icon = this->icons; + p.drawControl((QStyle::ControlElement)CustomControlElement::CE_ExtendedCheckBox, opt); + //QStyle* style = QApplication::style(); + //style->drawComplexControl((QStyle::ComplexControl)CC_MeterSlider, &sliderComplex2, &painter, this); +} + +void ExtendedCheckBox::addIcon(char* const path, QIcon::State state) { + QString str(path); + QSvgRenderer rr(str); + QPixmap pixmap(64, 64); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + rr.render(&painter); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + uint8_t a, r, g, b; + if (StylingHelper::argbToDiscreteValues(osh->getAccentColor(), &r, &g, &b, &a)) { + QColor color(r, g, b, a); + painter.fillRect(pixmap.rect(), color); + } + painter.end(); + icons.addPixmap(pixmap, QIcon::Normal, state); + //this->setIcon(icons); + //icons.addFile(":/Icons/images/second.svg",QSize(32,32),QIcon::Normal,QIcon::Off); +} + + QRect MainWindow::setSizePosition(QScreen* screen, int width, int height) { //setGeometry ignores decoration size, theres others for that QRect trayIconPos = this->trayIcon->geometry(); @@ -276,7 +306,7 @@ void MainWindow::compose(bool isVisible) { * Setting correct widget widths and heights */ log_to_file("[Compose]\n"); - screen = this->getCurrentScreen(); + screen = StylingHelper::getCurrentScreen(); log_debugcpp("Screen: " + screen->model().toStdString() + " " + screen->name().toStdString()); QRect screenRes = screen->geometry(); @@ -336,7 +366,7 @@ void MainWindow::compose(bool isVisible) { } QScreen* MainWindow::getCurrentScreen() { - //todo: Using cursor pos as screen detector. Flawed. + //note: Using cursor pos as screen detector. Flawed. QPoint cursorPos = QCursor::pos(); log_debugcpp("Cursor pos: " + std::to_string(cursorPos.ry()) + " " + std::to_string(cursorPos.rx())); @@ -363,7 +393,9 @@ SessionWidget::SessionWidget(uint64_t idx, SessionHandler* sh, QWidget *parent) widgetLayout->getContentsMargins(&left, &top, &right, &bottom); widgetLayout->setContentsMargins(0, top, 0, bottom); - muteButton = new QCheckBox(this); + muteButton = new ExtendedCheckBox(this); + muteButton->addIcon(":/assets/mute.svg", QIcon::On); + muteButton->addIcon(":/assets/unmute.svg", QIcon::Off); mainLabel = new QLabel(QString::fromStdWString(sh->getName()), this); mainLabel->setToolTip(QString::fromStdWString(sh->getName())); mainSlider = new MeterSlider(Qt::Horizontal, this); diff --git a/src/qt/qtclasses.h b/src/qt/qtclasses.h index 0d5733f..1b2658b 100644 --- a/src/qt/qtclasses.h +++ b/src/qt/qtclasses.h @@ -40,12 +40,17 @@ public: class ExtendedCheckBox : public QCheckBox { Q_OBJECT +private: + QIcon icons; + protected: void customEvent(QEvent* ev) override; + void paintEvent(QPaintEvent* event) override; public: //c++11: this inherits all parent's constructors unconditionally using QCheckBox::QCheckBox; + void addIcon(char* const path, QIcon::State state); //alternative being calling parent ctor directly after declaring child ctor: //B(int x) : A(x) { } }; @@ -68,7 +73,7 @@ private: MeterSlider *mainSlider = nullptr; uint64_t idx; QHBoxLayout *widgetLayout = nullptr; - QCheckBox *muteButton = nullptr; + ExtendedCheckBox *muteButton = nullptr; SessionHandler* sh; QTimer* volumePoller = nullptr; QSpacerItem* widthSpacer; diff --git a/src/qt/qtcommon.h b/src/qt/qtcommon.h index b059504..61873eb 100644 --- a/src/qt/qtcommon.h +++ b/src/qt/qtcommon.h @@ -44,6 +44,7 @@ #include #include #include +#include //#include //#include /* @@ -63,6 +64,10 @@ enum CustomComplexControl { CC_MeterSlider = 0xf0000001 }; +enum CustomControlElement { + CE_ExtendedCheckBox = 0xf0000001 +}; + namespace StylingHelper { static inline void setBackgroundColor(bool lightMode) { @@ -218,5 +223,32 @@ namespace StylingHelper { return pal.color(QPalette::Base); } + static inline QPixmap svg2Pixmap(const QString& svgContent, + const QSize& size, + QPainter::CompositionMode mode = QPainter::CompositionMode_SourceOver) + { + QSvgRenderer rr(svgContent); + QImage image(size.width(), size.height(), QImage::Format_ARGB32); + QPainter painter(&image); + painter.setCompositionMode(mode); + image.fill(Qt::transparent); + rr.render(&painter); + return QPixmap::fromImage(image); + } + + static inline QScreen* getCurrentScreen() { + //note: Using cursor pos as screen detector. Flawed. + QPoint cursorPos = QCursor::pos(); + + for (QScreen *screen : QGuiApplication::screens()) { + QRect screenRect = screen->geometry(); + if (screenRect.contains(cursorPos)) { + return screen; + } + } + + return QGuiApplication::primaryScreen(); + } + } diff --git a/src/qt/qtvisuals.h b/src/qt/qtvisuals.h index 0db58e8..8902ed1 100644 --- a/src/qt/qtvisuals.h +++ b/src/qt/qtvisuals.h @@ -20,8 +20,10 @@ public: return baseStyle()->styleHint(hint, option, widget, returnData); } - QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, - SubControl subControl, const QWidget *widget) const { + QRect subControlRect(ComplexControl control, + const QStyleOptionComplex *option, + SubControl subControl, + const QWidget *widget) const { QRect rect = QCommonStyle::subControlRect(CC_Slider, option, subControl, widget); switch (control) { @@ -186,6 +188,51 @@ public: } + void drawControl(ControlElement element, const QStyleOption *opt, + QPainter *p, const QWidget *widget) const + { + switch(element) { + case CE_ExtendedCheckBox: + if (const QStyleOptionButton *btn = qstyleoption_cast(opt)) { + QStyleOptionButton subopt = *btn; + subopt.rect = subElementRect(SE_CheckBoxIndicator, btn, widget); + //proxy()->drawPrimitive(PE_IndicatorCheckBox, &subopt, p, widget); + subopt.rect = subElementRect(SE_CheckBoxContents, btn, widget); + + //proxy()->drawControl(CE_CheckBoxLabel, &subopt, p, widget); + int alignment = visualAlignment(btn->direction, Qt::AlignLeft | Qt::AlignVCenter); + + if (!proxy()->styleHint(SH_UnderlineShortcut, btn, widget)) + alignment |= Qt::TextHideMnemonic; + QPixmap pix; + QRect textRect = btn->rect; + if (!btn->icon.isNull()) { + pix = btn->icon.pixmap(btn->iconSize, StylingHelper::getCurrentScreen()->devicePixelRatio(), + QIcon::Mode::Normal, btn->state & State_On ? QIcon::On : QIcon::Off); + proxy()->drawItemPixmap(p, btn->rect, alignment, pix); + if (btn->direction == Qt::RightToLeft) + textRect.setRight(textRect.right() - btn->iconSize.width() - 4); + else + textRect.setLeft(textRect.left() + btn->iconSize.width() + 4); + } + if (!btn->text.isEmpty()){ + proxy()->drawItemText(p, textRect, alignment | Qt::TextShowMnemonic, + btn->palette, btn->state & State_Enabled, btn->text, QPalette::WindowText); + } + // + if (btn->state & State_HasFocus) { + QStyleOptionFocusRect fropt; + fropt.QStyleOption::operator=(*btn); + fropt.rect = subElementRect(SE_CheckBoxFocusRect, btn, widget); + proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget); + } + } + default: + baseStyle()->drawControl(element, opt, p, widget); + break; + } + } + void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const { QColor outline;