LCOV - code coverage report
Current view: top level - muse/framework/vst/qml/Muse/Vst - vstview.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 168 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "vstview.h"
      23              : 
      24              : #include <QQuickWindow>
      25              : 
      26              : #include "ivstplugininstance.h"
      27              : 
      28              : #ifdef Q_OS_WIN
      29              : #include <windows.h>
      30              : #endif
      31              : 
      32              : #ifdef Q_OS_LINUX
      33              : #define USE_LINUX_RUNLOOP
      34              : #endif
      35              : 
      36              : #ifdef USE_LINUX_RUNLOOP
      37              : #include "platform/linux/runloop.h"
      38              : #endif
      39              : 
      40              : #include "global/types/number.h"
      41              : #include "log.h"
      42              : 
      43              : using namespace muse::vst;
      44              : 
      45              : #ifdef Q_OS_WIN
      46              : static void removeMinMaxButtons(HWND window)
      47              : {
      48              :     IF_ASSERT_FAILED(window && IsWindow(window)) {
      49              :         return;
      50              :     }
      51              : 
      52              :     // Remove the minimize & maximize buttons
      53              :     LONG style = GetWindowLong(window, GWL_STYLE);
      54              :     style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
      55              :     SetWindowLong(window, GWL_STYLE, style);
      56              : 
      57              :     // Force redraw of non-client area
      58              :     SetWindowPos(window, nullptr, 0, 0, 0, 0,
      59              :                  SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
      60              : }
      61              : 
      62              : #endif
      63              : 
      64            0 : ::Steinberg::uint32 PLUGIN_API VstView::addRef()
      65              : {
      66            0 :     return ::Steinberg::FUnknownPrivate::atomicAdd(__funknownRefCount, 1);
      67              : }
      68              : 
      69            0 : ::Steinberg::uint32 PLUGIN_API VstView::release()
      70              : {
      71            0 :     if (::Steinberg::FUnknownPrivate::atomicAdd(__funknownRefCount, -1) == 0) {
      72            0 :         return 0;
      73              :     }
      74            0 :     return __funknownRefCount;
      75              : }
      76              : 
      77            0 : Steinberg::tresult VstView::queryInterface(const ::Steinberg::TUID _iid, void** obj)
      78              : {
      79            0 :     QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPlugFrame);
      80            0 :     QUERY_INTERFACE(_iid, obj, Steinberg::IPlugFrame::iid, Steinberg::IPlugFrame);
      81              :     //As VST3 documentation states, IPlugFrame also has to provide
      82              :     //reference to the Steinberg::Linux::IRunLoop implementation.
      83              : #ifdef USE_LINUX_RUNLOOP
      84            0 :     if (m_runLoop && Steinberg::FUnknownPrivate::iidEqual(_iid, Steinberg::Linux::IRunLoop::iid)) {
      85            0 :         m_runLoop->addRef();
      86            0 :         *obj = static_cast<Steinberg::Linux::IRunLoop*>(m_runLoop);
      87            0 :         return ::Steinberg::kResultOk;
      88              :     }
      89              : #endif
      90            0 :     *obj = nullptr;
      91            0 :     return ::Steinberg::kNoInterface;
      92              : }
      93              : 
      94            0 : static FIDString currentPlatformUiType()
      95              : {
      96              : #ifdef Q_OS_MAC
      97              :     return Steinberg::kPlatformTypeNSView;
      98              : #elif defined(Q_OS_IOS)
      99              :     return Steinberg::kPlatformTypeUIView;
     100              : #elif defined(Q_OS_WIN)
     101              :     return Steinberg::kPlatformTypeHWND;
     102              : #else
     103            0 :     return Steinberg::kPlatformTypeX11EmbedWindowID;
     104              : #endif
     105              : }
     106              : 
     107            0 : VstView::VstView(QQuickItem* parent)
     108            0 :     : QQuickItem(parent)
     109              : {
     110            0 :     FUNKNOWN_CTOR; // IPlugFrame
     111              : 
     112              : #ifdef USE_LINUX_RUNLOOP
     113            0 :     m_runLoop = new RunLoop();
     114              : #endif
     115            0 : }
     116              : 
     117            0 : VstView::~VstView()
     118              : {
     119              :     FUNKNOWN_DTOR; // IPlugFrame
     120              : 
     121            0 :     deinit();
     122            0 : }
     123              : 
     124            0 : void VstView::init()
     125              : {
     126            0 :     m_instance = instancesRegister()->instanceById(m_instanceId);
     127            0 :     IF_ASSERT_FAILED(m_instance) {
     128            0 :         return;
     129              :     }
     130              : 
     131            0 :     m_title = QString::fromStdString(m_instance->name());
     132            0 :     emit titleChanged();
     133              : 
     134            0 :     m_view = m_instance->createView();
     135            0 :     if (!m_view) {
     136            0 :         return;
     137              :     }
     138              : 
     139            0 :     if (m_view->isPlatformTypeSupported(currentPlatformUiType()) != Steinberg::kResultTrue) {
     140            0 :         return;
     141              :     }
     142              : 
     143            0 :     updateScreenMetrics(window()->screen());
     144              : 
     145            0 :     m_view->setFrame(this);
     146              : 
     147            0 :     m_vstWindow = new QWindow(window());
     148              : 
     149              :     Steinberg::tresult attached;
     150            0 :     attached = m_view->attached(reinterpret_cast<void*>(m_vstWindow->winId()), currentPlatformUiType());
     151            0 :     if (attached != Steinberg::kResultOk) {
     152            0 :         LOGE() << "Unable to attach vst plugin view to window"
     153            0 :                << ", instance name: " << m_instance->name();
     154            0 :         return;
     155              :     }
     156              : 
     157            0 :     connect(window(), &QWindow::screenChanged, this, [this](QScreen* screen) {
     158            0 :         updateScreenMetrics(screen);
     159              : 
     160            0 :         QMetaObject::invokeMethod(this, [this] {
     161            0 :             updateViewGeometry();
     162            0 :         }, Qt::QueuedConnection);
     163            0 :     });
     164              : 
     165              :     // Do not only rely on `QWindow::screenChanged` signal, which often does not get emitted though it should.
     166              :     // Proactively check for screen resolution changes.
     167            0 :     connect(&m_screenMetricsTimer, &QTimer::timeout, this, [this]() {
     168            0 :         QWindow* win = window();
     169            0 :         if (!win) {
     170            0 :             return;
     171              :         }
     172            0 :         QScreen* screen = win->screen();
     173            0 :         if (m_currentScreen != screen || m_screenMetrics.availableSize != screen->availableSize()
     174            0 :             || !is_equal(m_screenMetrics.devicePixelRatio, screen->devicePixelRatio())) {
     175            0 :             updateScreenMetrics(screen);
     176            0 :             updateViewGeometry();
     177              :         }
     178              :     });
     179            0 :     m_screenMetricsTimer.start(std::chrono::milliseconds { 100 });
     180              : 
     181            0 :     updateViewGeometry();
     182              : 
     183            0 :     m_vstWindow->show();
     184              : 
     185              : #ifdef Q_OS_WIN
     186              :     HWND winHwnd = reinterpret_cast<HWND>(m_vstWindow->winId());
     187              :     HWND viewHwnd = GetWindow(winHwnd, GW_CHILD);
     188              :     HWND rootHandle = GetAncestor(winHwnd, GA_ROOT);
     189              :     m_rootHandle = rootHandle;
     190              : 
     191              :     if (viewHwnd && IsWindow(viewHwnd)) {
     192              :         m_plugViewHandle = viewHwnd;
     193              :         qApp->installNativeEventFilter(this);
     194              :     }
     195              : 
     196              :     removeMinMaxButtons(rootHandle);
     197              : #endif
     198              : 
     199              : #ifdef Q_OS_MAC
     200              :     window()->installEventFilter(this);
     201              : #endif
     202              : }
     203              : 
     204            0 : void VstView::deinit()
     205              : {
     206              : #ifdef Q_OS_WIN
     207              :     qApp->removeNativeEventFilter(this);
     208              : #endif
     209              : 
     210            0 :     m_screenMetricsTimer.stop();
     211              : 
     212            0 :     deinitPluginView();
     213              : 
     214            0 :     m_plugViewHandle = nullptr;
     215            0 :     m_rootHandle = nullptr;
     216              : 
     217            0 :     if (m_vstWindow) {
     218            0 :         m_vstWindow->hide();
     219            0 :         delete m_vstWindow;
     220            0 :         m_vstWindow = nullptr;
     221              :     }
     222              : 
     223              : #ifdef USE_LINUX_RUNLOOP
     224            0 :     if (m_runLoop) {
     225            0 :         m_runLoop->stop();
     226            0 :         delete m_runLoop;
     227              :     }
     228              : #endif
     229              : 
     230            0 :     if (m_instance) {
     231            0 :         m_instance->refreshConfig();
     232            0 :         m_instance = nullptr;
     233              :     }
     234            0 : }
     235              : 
     236            0 : void VstView::deinitPluginView()
     237              : {
     238            0 :     if (m_view) {
     239            0 :         m_view->setFrame(nullptr);
     240            0 :         m_view->removed();
     241            0 :         m_view = nullptr;
     242              :     }
     243            0 : }
     244              : 
     245            0 : Steinberg::tresult VstView::resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* requiredSize)
     246              : {
     247            0 :     IF_ASSERT_FAILED(m_vstWindow) {
     248            0 :         return Steinberg::kResultFalse;
     249              :     }
     250              : 
     251              :     // anti recursion
     252            0 :     if (m_resizeViewCalled) {
     253            0 :         return Steinberg::kResultTrue;
     254              :     }
     255              : 
     256            0 :     m_resizeViewCalled = true;
     257              : 
     258            0 :     view->checkSizeConstraint(requiredSize);
     259              : 
     260            0 :     const int titleBarHeight = window()->frameGeometry().height() - window()->geometry().height();
     261              : 
     262            0 :     const int requiredWidth = requiredSize->getWidth() / m_screenMetrics.devicePixelRatio;
     263            0 :     const int requiredHeight = requiredSize->getHeight() / m_screenMetrics.devicePixelRatio;
     264              : 
     265            0 :     const int availableWidth = m_screenMetrics.availableSize.width() - 2 * m_sidePadding;
     266            0 :     const int availableHeight = m_screenMetrics.availableSize.height() - titleBarHeight - m_topPadding - m_bottomPadding;
     267              : 
     268            0 :     const int newWidth = std::min(requiredWidth, availableWidth);
     269            0 :     const int newHeight = std::min(requiredHeight, availableHeight);
     270              : 
     271            0 :     const int implicitWidth = std::max(m_minimumWidth, newWidth);
     272            0 :     setImplicitWidth(implicitWidth);
     273            0 :     setImplicitHeight(newHeight);
     274              : 
     275            0 :     const int sidePadding = std::max(m_sidePadding, (implicitWidth - newWidth) / 2);
     276            0 :     m_vstWindow->setGeometry(sidePadding, m_topPadding, newWidth, newHeight);
     277              : 
     278            0 :     Steinberg::ViewRect vstSize;
     279            0 :     vstSize.right = newWidth * m_screenMetrics.devicePixelRatio;
     280            0 :     vstSize.bottom = newHeight * m_screenMetrics.devicePixelRatio;
     281              :     // Some VST plugins won't cooperate and size down, in which case their UI will be clipped
     282              :     // by `m_vstWindow`. This is better than an overflow, because the host might want to place
     283              :     // control buttons below the UI, and these ought not to be hidden.
     284            0 :     view->onSize(&vstSize);
     285              : 
     286            0 :     m_resizeViewCalled = false;
     287              : 
     288            0 :     return Steinberg::kResultTrue;
     289              : }
     290              : 
     291            0 : bool VstView::eventFilter(QObject* watched, QEvent* event)
     292              : {
     293            0 :     if (event->type() == QEvent::Close) {
     294            0 :         deinitPluginView();
     295              :     }
     296              : 
     297            0 :     return QQuickItem::eventFilter(watched, event);
     298              : }
     299              : 
     300            0 : bool VstView::nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result)
     301              : {
     302              : #ifdef Q_OS_WIN
     303              :     if (!m_vstWindow || !m_vstWindow->isVisible() || !m_plugViewHandle || !m_rootHandle) {
     304              :         return false;
     305              :     }
     306              : 
     307              :     if (eventType != QByteArrayLiteral("windows_generic_MSG")) {
     308              :         return false;
     309              :     }
     310              : 
     311              :     const MSG* msg = static_cast<MSG*>(message);
     312              :     if (!msg->hwnd) {
     313              :         return false;
     314              :     }
     315              :     if (msg->message != WM_ERASEBKGND && msg->message != WM_WINDOWPOSCHANGED) {
     316              :         return false;
     317              :     }
     318              : 
     319              :     if (msg->hwnd != m_rootHandle) {
     320              :         const HWND root = GetAncestor(msg->hwnd, GA_ROOT);
     321              :         if (root != m_rootHandle) {
     322              :             return false;
     323              :         }
     324              :     }
     325              : 
     326              :     if (msg->message == WM_ERASEBKGND) {
     327              :         *result = 1;
     328              :         return true; // tell Windows: "already erased"
     329              :     }
     330              : 
     331              :     if (msg->message == WM_WINDOWPOSCHANGED && !m_updatePending) {
     332              :         m_updatePending = true;
     333              :         QTimer::singleShot(16, this, [this]() { // ~60 FPS
     334              :             m_updatePending = false;
     335              : 
     336              :             HWND hwnd = reinterpret_cast<HWND>(m_plugViewHandle);
     337              :             if (!hwnd || !IsWindow(hwnd)) {
     338              :                 m_plugViewHandle = nullptr;
     339              :                 return;
     340              :             }
     341              : 
     342              :             RedrawWindow(hwnd, nullptr, nullptr,
     343              :                          RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOERASE);
     344              :         });
     345              :     }
     346              : #else
     347              :     Q_UNUSED(eventType);
     348              :     Q_UNUSED(message);
     349              :     Q_UNUSED(result);
     350              : #endif
     351              : 
     352            0 :     return false;
     353              : }
     354              : 
     355            0 : void VstView::updateScreenMetrics(QScreen* screen)
     356              : {
     357            0 :     m_currentScreen = screen;
     358            0 :     m_screenMetrics.availableSize = m_currentScreen->availableSize();
     359              : #ifdef Q_OS_MAC
     360              :     constexpr auto devicePixelRatio = 1.0;
     361              : #else
     362            0 :     const auto devicePixelRatio = m_currentScreen->devicePixelRatio();
     363              : #endif
     364            0 :     m_screenMetrics.devicePixelRatio = devicePixelRatio;
     365              : 
     366              : #ifdef Q_OS_WIN
     367              :     Steinberg::FUnknownPtr<IPluginContentScaleHandler> scalingHandler(m_view);
     368              :     if (scalingHandler) {
     369              :         scalingHandler->setContentScaleFactor(m_screenMetrics.devicePixelRatio);
     370              :     }
     371              : #endif
     372            0 : }
     373              : 
     374            0 : void VstView::updateViewGeometry()
     375              : {
     376            0 :     if (!m_view) {
     377            0 :         return;
     378              :     }
     379              : 
     380            0 :     Steinberg::ViewRect size;
     381            0 :     m_view->getSize(&size);
     382              : 
     383            0 :     resizeView(m_view, &size);
     384              : }
     385              : 
     386            0 : int VstView::instanceId() const
     387              : {
     388            0 :     return m_instanceId;
     389              : }
     390              : 
     391            0 : void VstView::setInstanceId(int newInstanceId)
     392              : {
     393            0 :     if (m_instanceId == newInstanceId) {
     394            0 :         return;
     395              :     }
     396            0 :     m_instanceId = newInstanceId;
     397            0 :     emit instanceIdChanged();
     398              : }
     399              : 
     400            0 : QString VstView::title() const
     401              : {
     402            0 :     return m_title;
     403              : }
     404              : 
     405            0 : int VstView::sidePadding() const
     406              : {
     407            0 :     return m_sidePadding;
     408              : }
     409              : 
     410            0 : void VstView::setsidePadding(int sidePadding)
     411              : {
     412            0 :     if (m_sidePadding == sidePadding) {
     413            0 :         return;
     414              :     }
     415            0 :     m_sidePadding = sidePadding;
     416            0 :     emit sidePaddingChanged();
     417            0 :     updateViewGeometry();
     418              : }
     419              : 
     420            0 : int VstView::topPadding() const
     421              : {
     422            0 :     return m_topPadding;
     423              : }
     424              : 
     425            0 : void VstView::setTopPadding(int topPadding)
     426              : {
     427            0 :     if (m_topPadding == topPadding) {
     428            0 :         return;
     429              :     }
     430            0 :     m_topPadding = topPadding;
     431            0 :     emit topPaddingChanged();
     432            0 :     updateViewGeometry();
     433              : }
     434              : 
     435            0 : int VstView::bottomPadding() const
     436              : {
     437            0 :     return m_bottomPadding;
     438              : }
     439              : 
     440            0 : void VstView::setBottomPadding(int bottomPadding)
     441              : {
     442            0 :     if (m_bottomPadding == bottomPadding) {
     443            0 :         return;
     444              :     }
     445            0 :     m_bottomPadding = bottomPadding;
     446            0 :     emit bottomPaddingChanged();
     447            0 :     updateViewGeometry();
     448              : }
     449              : 
     450            0 : int VstView::minimumWidth() const
     451              : {
     452            0 :     return m_minimumWidth;
     453              : }
     454              : 
     455            0 : void VstView::setMinimumWidth(int minimumWidth)
     456              : {
     457            0 :     if (m_minimumWidth == minimumWidth) {
     458            0 :         return;
     459              :     }
     460            0 :     m_minimumWidth = minimumWidth;
     461            0 :     emit minimumWidthChanged();
     462            0 :     updateViewGeometry();
     463              : }
        

Generated by: LCOV version 2.0-1