| @@ -97,7 +97,7 @@ using namespace Steinberg; | |||
| #endif | |||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| extern JUCE_API double getScaleFactorForWindow (HWND); | |||
| double getScaleFactorForWindow (HWND); | |||
| #endif | |||
| //============================================================================== | |||
| @@ -1333,7 +1333,6 @@ private: | |||
| //============================================================================== | |||
| struct VST3PluginWindow : public AudioProcessorEditor, | |||
| private ComponentMovementWatcher, | |||
| private ComponentPeer::ScaleFactorListener, | |||
| private IPlugFrame | |||
| { | |||
| VST3PluginWindow (AudioPluginInstance* owner, IPlugView* pluginView) | |||
| @@ -1360,8 +1359,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
| if (scaleInterface != nullptr) | |||
| scaleInterface->release(); | |||
| removeScaleFactorListener(); | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| embeddedComponent.removeClient(); | |||
| #endif | |||
| @@ -1421,16 +1418,19 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
| private: | |||
| //============================================================================== | |||
| void componentPeerChanged() override | |||
| void componentPeerChanged() override {} | |||
| /* Convert from the component's coordinate system to the hosted VST3's coordinate system. */ | |||
| ViewRect componentToVST3Rect (Rectangle<int> r) const | |||
| { | |||
| removeScaleFactorListener(); | |||
| currentPeer = getTopLevelComponent()->getPeer(); | |||
| const auto physical = localAreaToGlobal (r) * nativeScaleFactor * getDesktopScaleFactor(); | |||
| return { 0, 0, physical.getWidth(), physical.getHeight() }; | |||
| } | |||
| if (currentPeer != nullptr) | |||
| { | |||
| currentPeer->addScaleFactorListener (this); | |||
| nativeScaleFactor = (float) currentPeer->getPlatformScaleFactor(); | |||
| } | |||
| /* Convert from the hosted VST3's coordinate system to the component's coordinate system. */ | |||
| Rectangle<int> vst3ToComponentRect (const ViewRect& vr) const | |||
| { | |||
| return getLocalArea (nullptr, Rectangle<int> { vr.right, vr.bottom } / (nativeScaleFactor * getDesktopScaleFactor())); | |||
| } | |||
| void componentMovedOrResized (bool, bool wasResized) override | |||
| @@ -1438,44 +1438,34 @@ private: | |||
| if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) | |||
| return; | |||
| ViewRect rect; | |||
| if (view->canResize() == kResultTrue) | |||
| { | |||
| rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); | |||
| rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); | |||
| auto rect = componentToVST3Rect (getLocalBounds()); | |||
| view->checkSizeConstraint (&rect); | |||
| { | |||
| const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | |||
| setSize (roundToInt ((float) rect.getWidth() / nativeScaleFactor), | |||
| roundToInt ((float) rect.getHeight() / nativeScaleFactor)); | |||
| const auto logicalSize = vst3ToComponentRect (rect); | |||
| setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||
| } | |||
| #if JUCE_WINDOWS | |||
| setPluginWindowPos (rect); | |||
| #else | |||
| embeddedComponent.setBounds (getLocalBounds()); | |||
| #endif | |||
| view->onSize (&rect); | |||
| } | |||
| else | |||
| { | |||
| ViewRect rect; | |||
| warnOnFailure (view->getSize (&rect)); | |||
| #if JUCE_WINDOWS | |||
| setPluginWindowPos (rect); | |||
| #else | |||
| resizeWithRect (embeddedComponent, rect, nativeScaleFactor); | |||
| #endif | |||
| resizeWithRect (embeddedComponent, rect); | |||
| } | |||
| // Some plugins don't update their cursor correctly when mousing out the window | |||
| Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | |||
| } | |||
| using ComponentMovementWatcher::componentMovedOrResized; | |||
| void componentVisibilityChanged() override | |||
| @@ -1484,20 +1474,14 @@ private: | |||
| resizeToFit(); | |||
| componentMovedOrResized (true, true); | |||
| } | |||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||
| void nativeScaleFactorChanged (double newScaleFactor) override | |||
| { | |||
| nativeScaleFactor = (float) newScaleFactor; | |||
| updatePluginScale(); | |||
| componentMovedOrResized (false, true); | |||
| } | |||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||
| void resizeToFit() | |||
| { | |||
| ViewRect rect; | |||
| warnOnFailure (view->getSize (&rect)); | |||
| resizeWithRect (*this, rect, nativeScaleFactor); | |||
| resizeWithRect (*this, rect); | |||
| } | |||
| tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override | |||
| @@ -1506,34 +1490,28 @@ private: | |||
| if (incomingView != nullptr && newSize != nullptr && incomingView == view) | |||
| { | |||
| auto scaleToViewRect = [this] (int dimension) | |||
| { | |||
| return (Steinberg::int32) roundToInt ((float) dimension * nativeScaleFactor); | |||
| }; | |||
| auto oldWidth = scaleToViewRect (getWidth()); | |||
| auto oldHeight = scaleToViewRect (getHeight()); | |||
| resizeWithRect (embeddedComponent, *newSize, nativeScaleFactor); | |||
| const auto oldPhysicalSize = componentToVST3Rect (getLocalBounds()); | |||
| const auto logicalSize = vst3ToComponentRect (*newSize); | |||
| setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||
| embeddedComponent.setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||
| #if JUCE_WINDOWS | |||
| setPluginWindowPos (*newSize); | |||
| embeddedComponent.updateHWNDBounds(); | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| embeddedComponent.updateEmbeddedBounds(); | |||
| #endif | |||
| setSize (embeddedComponent.getWidth(), embeddedComponent.getHeight()); | |||
| // According to the VST3 Workflow Diagrams, a resizeView from the plugin should | |||
| // always trigger a response from the host which confirms the new size. | |||
| ViewRect rect { 0, 0, | |||
| scaleToViewRect (getWidth()), | |||
| scaleToViewRect (getHeight()) }; | |||
| auto currentPhysicalSize = componentToVST3Rect (getLocalBounds()); | |||
| if (rect.right != oldWidth || rect.bottom != oldHeight | |||
| if (currentPhysicalSize.getWidth() != oldPhysicalSize.getWidth() | |||
| || currentPhysicalSize.getHeight() != oldPhysicalSize.getHeight() | |||
| || ! isInOnSize) | |||
| { | |||
| // Guard against plug-ins immediately calling resizeView() with the same size | |||
| const ScopedValueSetter<bool> inOnSizeSetter (isInOnSize, true); | |||
| view->onSize (&rect); | |||
| view->onSize (¤tPhysicalSize); | |||
| } | |||
| return kResultTrue; | |||
| @@ -1544,10 +1522,11 @@ private: | |||
| } | |||
| //============================================================================== | |||
| static void resizeWithRect (Component& comp, const ViewRect& rect, float scaleFactor) | |||
| void resizeWithRect (Component& comp, const ViewRect& rect) const | |||
| { | |||
| comp.setSize (jmax (10, std::abs (roundToInt ((float) rect.getWidth() / scaleFactor))), | |||
| jmax (10, std::abs (roundToInt ((float) rect.getHeight() / scaleFactor)))); | |||
| const auto logicalSize = vst3ToComponentRect (rect); | |||
| comp.setSize (jmax (10, logicalSize.getWidth()), | |||
| jmax (10, logicalSize.getHeight())); | |||
| } | |||
| void attachPluginWindow() | |||
| @@ -1555,19 +1534,16 @@ private: | |||
| if (pluginHandle == HandleFormat{}) | |||
| { | |||
| #if JUCE_WINDOWS | |||
| if (auto* topComp = getTopLevelComponent()) | |||
| { | |||
| peer.reset (embeddedComponent.createNewPeer (0, topComp->getWindowHandle())); | |||
| pluginHandle = (HandleFormat) peer->getNativeHandle(); | |||
| } | |||
| #else | |||
| pluginHandle = static_cast<HWND> (embeddedComponent.getHWND()); | |||
| #endif | |||
| embeddedComponent.setBounds (getLocalBounds()); | |||
| addAndMakeVisible (embeddedComponent); | |||
| #if JUCE_MAC | |||
| pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||
| #endif | |||
| #if JUCE_MAC | |||
| pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||
| #endif | |||
| if (pluginHandle == HandleFormat{}) | |||
| @@ -1586,16 +1562,6 @@ private: | |||
| } | |||
| } | |||
| void removeScaleFactorListener() | |||
| { | |||
| if (currentPeer == nullptr) | |||
| return; | |||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||
| if (ComponentPeer::getPeer (i) == currentPeer) | |||
| currentPeer->removeScaleFactorListener (this); | |||
| } | |||
| void updatePluginScale() | |||
| { | |||
| if (scaleInterface != nullptr) | |||
| @@ -1608,7 +1574,7 @@ private: | |||
| { | |||
| if (scaleInterface != nullptr) | |||
| { | |||
| const auto result = scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | |||
| const auto result = scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getEffectiveScale()); | |||
| ignoreUnused (result); | |||
| #if ! JUCE_MAC | |||
| @@ -1617,38 +1583,49 @@ private: | |||
| } | |||
| } | |||
| void setScaleFactor (float s) override | |||
| { | |||
| userScaleFactor = s; | |||
| setContentScaleFactor(); | |||
| resizeToFit(); | |||
| } | |||
| float getEffectiveScale() const | |||
| { | |||
| return nativeScaleFactor * userScaleFactor; | |||
| } | |||
| //============================================================================== | |||
| Atomic<int> refCount { 1 }; | |||
| VSTComSmartPtr<IPlugView> view; | |||
| #if JUCE_WINDOWS | |||
| struct ChildComponent : public Component | |||
| { | |||
| ChildComponent() { setOpaque (true); } | |||
| void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } | |||
| using Component::createNewPeer; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) | |||
| }; | |||
| using HandleFormat = HWND; | |||
| void setPluginWindowPos (ViewRect rect) | |||
| struct ViewComponent : public HWNDComponent | |||
| { | |||
| if (auto* topComp = getTopLevelComponent()) | |||
| ViewComponent() | |||
| { | |||
| auto pos = (topComp->getLocalPoint (this, Point<int>()) * nativeScaleFactor).roundToInt(); | |||
| setOpaque (true); | |||
| inner.addToDesktop (0); | |||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHandle }; | |||
| SetWindowPos (pluginHandle, nullptr, | |||
| pos.x, pos.y, | |||
| rect.getWidth(), rect.getHeight(), | |||
| isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
| if (auto* peer = inner.getPeer()) | |||
| setHWND (peer->getNativeHandle()); | |||
| } | |||
| } | |||
| ChildComponent embeddedComponent; | |||
| std::unique_ptr<ComponentPeer> peer; | |||
| using HandleFormat = HWND; | |||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||
| private: | |||
| struct Inner : public Component | |||
| { | |||
| Inner() { setOpaque (true); } | |||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||
| }; | |||
| Inner inner; | |||
| }; | |||
| ViewComponent embeddedComponent; | |||
| #elif JUCE_MAC | |||
| NSViewComponentWithParent embeddedComponent; | |||
| using HandleFormat = NSView*; | |||
| @@ -1664,9 +1641,35 @@ private: | |||
| HandleFormat pluginHandle = {}; | |||
| bool recursiveResize = false, isInOnSize = false, attachedCalled = false; | |||
| ComponentPeer* currentPeer = nullptr; | |||
| Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | |||
| float nativeScaleFactor = 1.0f; | |||
| float userScaleFactor = 1.0f; | |||
| struct ScaleNotifierCallback | |||
| { | |||
| VST3PluginWindow& window; | |||
| void operator() (float platformScale) const | |||
| { | |||
| MessageManager::callAsync ([ref = Component::SafePointer<VST3PluginWindow> (&window), platformScale] | |||
| { | |||
| if (auto* r = ref.getComponent()) | |||
| { | |||
| r->nativeScaleFactor = platformScale; | |||
| r->setContentScaleFactor(); | |||
| r->resizeToFit(); | |||
| #if JUCE_WINDOWS | |||
| r->embeddedComponent.updateHWNDBounds(); | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| r->embeddedComponent.updateEmbeddedBounds(); | |||
| #endif | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } }; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | |||
| @@ -2206,32 +2209,7 @@ public: | |||
| ~VST3PluginInstance() override | |||
| { | |||
| struct VST3Deleter : public CallbackMessage | |||
| { | |||
| VST3Deleter (VST3PluginInstance& inInstance, WaitableEvent& inEvent) | |||
| : vst3Instance (inInstance), completionSignal (inEvent) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| vst3Instance.cleanup(); | |||
| completionSignal.signal(); | |||
| } | |||
| VST3PluginInstance& vst3Instance; | |||
| WaitableEvent& completionSignal; | |||
| }; | |||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
| { | |||
| cleanup(); | |||
| } | |||
| else | |||
| { | |||
| WaitableEvent completionEvent; | |||
| (new VST3Deleter (*this, completionEvent))->post(); | |||
| completionEvent.wait(); | |||
| } | |||
| callOnMessageThread ([this] { cleanup(); }); | |||
| } | |||
| void cleanup() | |||
| @@ -2988,7 +2966,6 @@ public: | |||
| ignoreUnused (data, sizeInBytes); | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| @@ -3318,7 +3295,7 @@ private: | |||
| /** @note An IPlugView, when first created, should start with a ref-count of 1! */ | |||
| IPlugView* tryCreatingView() const | |||
| { | |||
| JUCE_ASSERT_MESSAGE_THREAD | |||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
| IPlugView* v = editController->createView (Vst::ViewType::kEditor); | |||
| @@ -1083,34 +1083,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, | |||
| ~VSTPluginInstance() override | |||
| { | |||
| if (vstEffect != nullptr && vstEffect->magic == 0x56737450 /* 'VstP' */) | |||
| { | |||
| struct VSTDeleter : public CallbackMessage | |||
| { | |||
| VSTDeleter (VSTPluginInstance& inInstance, WaitableEvent& inEvent) | |||
| : vstInstance (inInstance), completionSignal (inEvent) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| vstInstance.cleanup(); | |||
| completionSignal.signal(); | |||
| } | |||
| VSTPluginInstance& vstInstance; | |||
| WaitableEvent& completionSignal; | |||
| }; | |||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
| { | |||
| cleanup(); | |||
| } | |||
| else | |||
| { | |||
| WaitableEvent completionEvent; | |||
| (new VSTDeleter (*this, completionEvent))->post(); | |||
| completionEvent.wait(); | |||
| } | |||
| } | |||
| callOnMessageThread ([this] { cleanup(); }); | |||
| } | |||
| void cleanup() | |||
| @@ -1982,20 +1955,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, | |||
| return false; | |||
| } | |||
| bool updateSizeFromEditor (int w, int h) | |||
| { | |||
| editorSize = { w, h }; | |||
| if (auto* editor = getActiveEditor()) | |||
| { | |||
| editor->setSize (w, h); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| Rectangle<int> getEditorSize() const { return editorSize; } | |||
| bool updateSizeFromEditor (int w, int h); | |||
| Vst2::AEffect* vstEffect; | |||
| ModuleHandle::Ptr vstModule; | |||
| @@ -2076,7 +2036,6 @@ private: | |||
| AudioBuffer<double> tmpBufferDouble; | |||
| HeapBlock<double*> channelBufferDouble; | |||
| std::unique_ptr<VST2BypassParameter> bypassParam; | |||
| Rectangle<int> editorSize; | |||
| std::unique_ptr<VSTXMLInfo> xmlInfo; | |||
| @@ -2141,24 +2100,11 @@ private: | |||
| void setWindowSize (int width, int height) | |||
| { | |||
| if (auto* ed = getActiveEditor()) | |||
| { | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| const MessageManagerLock mmLock; | |||
| #endif | |||
| #if ! JUCE_MAC | |||
| if (auto* peer = ed->getTopLevelComponent()->getPeer()) | |||
| { | |||
| auto scale = peer->getPlatformScaleFactor(); | |||
| updateSizeFromEditor (roundToInt (width / scale), roundToInt (height / scale)); | |||
| return; | |||
| } | |||
| #endif | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| const MessageManagerLock mmLock; | |||
| #endif | |||
| updateSizeFromEditor (width, height); | |||
| } | |||
| updateSizeFromEditor (width, height); | |||
| } | |||
| //============================================================================== | |||
| @@ -2769,7 +2715,6 @@ static Array<VSTPluginWindow*> activeVSTWindows; | |||
| struct VSTPluginWindow : public AudioProcessorEditor, | |||
| #if ! JUCE_MAC | |||
| private ComponentMovementWatcher, | |||
| private ComponentPeer::ScaleFactorListener, | |||
| #endif | |||
| private Timer | |||
| { | |||
| @@ -2813,6 +2758,10 @@ public: | |||
| setOpaque (true); | |||
| setVisible (true); | |||
| #if JUCE_WINDOWS | |||
| addAndMakeVisible (embeddedComponent); | |||
| #endif | |||
| } | |||
| ~VSTPluginWindow() override | |||
| @@ -2824,8 +2773,6 @@ public: | |||
| carbonWrapper.reset(); | |||
| #endif | |||
| cocoaWrapper.reset(); | |||
| #else | |||
| removeScaleFactorListeners(); | |||
| #endif | |||
| activeVSTWindows.removeFirstMatchingValue (this); | |||
| @@ -2833,10 +2780,36 @@ public: | |||
| } | |||
| //============================================================================== | |||
| void updateSizeFromEditor (int w, int h) | |||
| /* Convert from the hosted VST's coordinate system to the component's coordinate system. */ | |||
| Rectangle<int> vstToComponentRect (Component& editor, const Rectangle<int>& vr) const | |||
| { | |||
| if (! plugin.updateSizeFromEditor (w, h)) | |||
| setSize (w, h); | |||
| return editor.getLocalArea (nullptr, vr / (nativeScaleFactor * getDesktopScaleFactor())); | |||
| } | |||
| Rectangle<int> componentToVstRect (Component& editor, const Rectangle<int>& vr) const | |||
| { | |||
| if (auto* tl = editor.getTopLevelComponent()) | |||
| return tl->getLocalArea (&editor, vr) * nativeScaleFactor * tl->getDesktopScaleFactor(); | |||
| return {}; | |||
| } | |||
| bool updateSizeFromEditor (int w, int h) | |||
| { | |||
| const auto correctedBounds = vstToComponentRect (*this, { w, h }); | |||
| setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||
| #if JUCE_MAC | |||
| #if JUCE_SUPPORT_CARBON | |||
| if (carbonWrapper != nullptr) | |||
| carbonWrapper->setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||
| #endif | |||
| if (cocoaWrapper != nullptr) | |||
| cocoaWrapper->setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||
| #endif | |||
| return true; | |||
| } | |||
| #if JUCE_MAC | |||
| @@ -2870,6 +2843,11 @@ public: | |||
| void parentHierarchyChanged() override { visibilityChanged(); } | |||
| #else | |||
| float getEffectiveScale() const | |||
| { | |||
| return nativeScaleFactor * userScaleFactor; | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| @@ -2877,7 +2855,7 @@ public: | |||
| { | |||
| if (pluginWindow != 0) | |||
| { | |||
| auto clip = g.getClipBounds(); | |||
| auto clip = componentToVstRect (*this, g.getClipBounds().toNearestInt()); | |||
| X11Symbols::getInstance()->xClearArea (display, pluginWindow, clip.getX(), clip.getY(), | |||
| static_cast<unsigned int> (clip.getWidth()), | |||
| @@ -2896,36 +2874,15 @@ public: | |||
| if (recursiveResize) | |||
| return; | |||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||
| if (getPeer() != nullptr) | |||
| { | |||
| const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | |||
| const auto pos = (peer->getAreaCoveredBy (*this).toFloat() * nativeScaleFactor).toNearestInt(); | |||
| #if JUCE_WINDOWS | |||
| if (pluginHWND != 0) | |||
| { | |||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||
| SetWindowPos (pluginHWND, | |||
| HWND_BOTTOM, | |||
| pos.getX(), | |||
| pos.getY(), | |||
| 0, | |||
| 0, | |||
| SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); | |||
| MessageManager::callAsync ([ref = SafePointer<Component> (this)] | |||
| { | |||
| // Clean up after the editor window, in case it tried to move itself | |||
| // into the wrong location over this component. | |||
| if (ref == nullptr) | |||
| return; | |||
| if (auto* p = ref->getPeer()) | |||
| p->repaint (p->getBounds().withPosition ({})); | |||
| }); | |||
| } | |||
| embeddedComponent.setBounds (getLocalBounds()); | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| const auto pos = componentToVstRect (*this, getLocalBounds()); | |||
| if (pluginWindow != 0) | |||
| { | |||
| auto* symbols = X11Symbols::getInstance(); | |||
| @@ -2951,8 +2908,7 @@ public: | |||
| else if (! shouldAvoidDeletingWindow()) | |||
| closePluginWindow(); | |||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||
| setScaleFactorAndDispatchMessage (peer->getPlatformScaleFactor()); | |||
| setContentScaleFactor(); | |||
| #if JUCE_LINUX || JUCE_BSD | |||
| MessageManager::callAsync ([safeThis = SafePointer<VSTPluginWindow> { this }] | |||
| @@ -2971,38 +2927,32 @@ public: | |||
| { | |||
| closePluginWindow(); | |||
| openPluginWindow(); | |||
| removeScaleFactorListeners(); | |||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||
| peer->addScaleFactorListener (this); | |||
| componentMovedOrResized (true, true); | |||
| } | |||
| void nativeScaleFactorChanged (double newScaleFactor) override | |||
| void setContentScaleFactor() | |||
| { | |||
| setScaleFactorAndDispatchMessage (newScaleFactor); | |||
| #if JUCE_WINDOWS | |||
| resizeToFit(); | |||
| #endif | |||
| } | |||
| void setScaleFactorAndDispatchMessage (double newScaleFactor) | |||
| { | |||
| if (approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||
| return; | |||
| nativeScaleFactor = (float) newScaleFactor; | |||
| if (pluginRespondsToDPIChanges) | |||
| dispatch (Vst2::effVendorSpecific, | |||
| (int) ByteOrder::bigEndianInt ("PreS"), | |||
| (int) ByteOrder::bigEndianInt ("AeCs"), | |||
| nullptr, nativeScaleFactor); | |||
| nullptr, getEffectiveScale()); | |||
| } | |||
| #endif | |||
| void setScaleFactor (float scale) override | |||
| { | |||
| userScaleFactor = scale; | |||
| #if ! JUCE_MAC | |||
| setContentScaleFactor(); | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| resizeToFit(); | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| bool keyStateChanged (bool) override { return pluginWantsKeys; } | |||
| bool keyPressed (const juce::KeyPress&) override { return pluginWantsKeys; } | |||
| @@ -3025,7 +2975,14 @@ public: | |||
| if (! reentrantGuard) | |||
| { | |||
| reentrantGuard = true; | |||
| #if JUCE_WINDOWS | |||
| // Some plugins may draw/resize inside their idle callback, so ensure that | |||
| // DPI awareness is set correctly inside this call. | |||
| ScopedThreadDPIAwarenessSetter scope (getPluginHWND()); | |||
| #endif | |||
| plugin.dispatch (Vst2::effEditIdle, 0, 0, nullptr, 0); | |||
| reentrantGuard = false; | |||
| } | |||
| @@ -3124,7 +3081,7 @@ private: | |||
| #else | |||
| void openPluginWindow() | |||
| { | |||
| if (isOpen || getWindowHandle() == nullptr) | |||
| if (isOpen) | |||
| return; | |||
| JUCE_VST_LOG ("Opening VST UI: " + plugin.getName()); | |||
| @@ -3132,13 +3089,18 @@ private: | |||
| pluginRespondsToDPIChanges = plugin.pluginCanDo ("supportsViewDpiScaling") > 0; | |||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||
| setScaleFactorAndDispatchMessage (peer->getPlatformScaleFactor()); | |||
| setContentScaleFactor(); | |||
| Vst2::ERect* rect = nullptr; | |||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | |||
| dispatch (Vst2::effEditOpen, 0, 0, getWindowHandle(), 0); | |||
| #if JUCE_WINDOWS | |||
| auto* handle = embeddedComponent.getHWND(); | |||
| #else | |||
| auto* handle = getWindowHandle(); | |||
| #endif | |||
| dispatch (Vst2::effEditOpen, 0, 0, handle, 0); | |||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); // do this before and after like in the steinberg example | |||
| dispatch (Vst2::effGetProgram, 0, 0, nullptr, 0); // also in steinberg code | |||
| @@ -3146,7 +3108,7 @@ private: | |||
| #if JUCE_WINDOWS | |||
| originalWndProc = 0; | |||
| pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); | |||
| auto* pluginHWND = getPluginHWND(); | |||
| if (pluginHWND == 0) | |||
| { | |||
| @@ -3189,7 +3151,7 @@ private: | |||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||
| SetWindowPos (pluginHWND, 0, | |||
| 0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | |||
| 0, 0, rw, rh, | |||
| SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); | |||
| GetWindowRect (pluginHWND, &r); | |||
| @@ -3225,9 +3187,6 @@ private: | |||
| X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | |||
| #endif | |||
| w = roundToInt ((float) w / nativeScaleFactor); | |||
| h = roundToInt ((float) h / nativeScaleFactor); | |||
| // double-check it's not too tiny | |||
| w = jmax (w, 32); | |||
| h = jmax (h, 32); | |||
| @@ -3241,13 +3200,6 @@ private: | |||
| startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); | |||
| repaint(); | |||
| } | |||
| void removeScaleFactorListeners() | |||
| { | |||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||
| if (auto* peer = ComponentPeer::getPeer (i)) | |||
| peer->removeScaleFactorListener (this); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| @@ -3266,12 +3218,13 @@ private: | |||
| #if JUCE_WINDOWS | |||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4244) | |||
| auto* pluginHWND = getPluginHWND(); | |||
| if (originalWndProc != 0 && pluginHWND != 0 && IsWindow (pluginHWND)) | |||
| SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); | |||
| JUCE_END_IGNORE_WARNINGS_MSVC | |||
| originalWndProc = 0; | |||
| pluginHWND = 0; | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| pluginWindow = 0; | |||
| #endif | |||
| @@ -3291,7 +3244,8 @@ private: | |||
| if (pluginRefusesToResize) | |||
| return true; | |||
| return (isWithin (w, getWidth(), 5) && isWithin (h, getHeight(), 5)); | |||
| const auto converted = vstToComponentRect (*this, { w, h }); | |||
| return (isWithin (converted.getWidth(), getWidth(), 5) && isWithin (converted.getHeight(), getHeight(), 5)); | |||
| } | |||
| void resizeToFit() | |||
| @@ -3299,14 +3253,16 @@ private: | |||
| Vst2::ERect* rect = nullptr; | |||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | |||
| auto w = roundToInt ((rect->right - rect->left) / nativeScaleFactor); | |||
| auto h = roundToInt ((rect->bottom - rect->top) / nativeScaleFactor); | |||
| auto w = rect->right - rect->left; | |||
| auto h = rect->bottom - rect->top; | |||
| if (! isWindowSizeCorrectForPlugin (w, h)) | |||
| { | |||
| updateSizeFromEditor (w, h); | |||
| sizeCheckCount = 0; | |||
| } | |||
| embeddedComponent.updateHWNDBounds(); | |||
| } | |||
| void checkPluginWindowSize() | |||
| @@ -3322,7 +3278,9 @@ private: | |||
| { | |||
| Component::SafePointer<VSTPluginWindow> w (activeVSTWindows[i]); | |||
| if (w != nullptr && w->pluginHWND == hW) | |||
| auto* pluginHWND = w->getPluginHWND(); | |||
| if (w != nullptr && pluginHWND == hW) | |||
| { | |||
| if (message == WM_CHAR | |||
| || message == WM_KEYDOWN | |||
| @@ -3337,7 +3295,7 @@ private: | |||
| if (w != nullptr) // (may have been deleted in SendMessage callback) | |||
| return CallWindowProc ((WNDPROC) w->originalWndProc, | |||
| (HWND) w->pluginHWND, | |||
| (HWND) pluginHWND, | |||
| message, wParam, lParam); | |||
| } | |||
| } | |||
| @@ -3439,32 +3397,82 @@ private: | |||
| void resized() override | |||
| { | |||
| #if JUCE_SUPPORT_CARBON | |||
| if (carbonWrapper != nullptr) | |||
| carbonWrapper->setSize (getWidth(), getHeight()); | |||
| #endif | |||
| if (cocoaWrapper != nullptr) | |||
| cocoaWrapper->setSize (getWidth(), getHeight()); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| VSTPluginInstance& plugin; | |||
| float userScaleFactor = 1.0f; | |||
| bool isOpen = false, recursiveResize = false; | |||
| bool pluginWantsKeys = false, pluginRefusesToResize = false, alreadyInside = false; | |||
| #if ! JUCE_MAC | |||
| bool pluginRespondsToDPIChanges = false; | |||
| float nativeScaleFactor = 1.0f; | |||
| struct ScaleNotifierCallback | |||
| { | |||
| VSTPluginWindow& window; | |||
| void operator() (float platformScale) const | |||
| { | |||
| MessageManager::callAsync ([ref = Component::SafePointer<VSTPluginWindow> (&window), platformScale] | |||
| { | |||
| if (auto* r = ref.getComponent()) | |||
| { | |||
| r->nativeScaleFactor = platformScale; | |||
| r->setContentScaleFactor(); | |||
| #if JUCE_WINDOWS | |||
| r->resizeToFit(); | |||
| #endif | |||
| r->componentMovedOrResized (true, true); | |||
| } | |||
| }); | |||
| } | |||
| }; | |||
| NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } }; | |||
| #if JUCE_WINDOWS | |||
| HWND pluginHWND = {}; | |||
| struct ViewComponent : public HWNDComponent | |||
| { | |||
| ViewComponent() | |||
| { | |||
| setOpaque (true); | |||
| inner.addToDesktop (0); | |||
| if (auto* peer = inner.getPeer()) | |||
| setHWND (peer->getNativeHandle()); | |||
| } | |||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||
| private: | |||
| struct Inner : public Component | |||
| { | |||
| Inner() { setOpaque (true); } | |||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||
| }; | |||
| Inner inner; | |||
| }; | |||
| HWND getPluginHWND() const | |||
| { | |||
| return GetWindow ((HWND) embeddedComponent.getHWND(), GW_CHILD); | |||
| } | |||
| ViewComponent embeddedComponent; | |||
| void* originalWndProc = {}; | |||
| int sizeCheckCount = 0; | |||
| #elif JUCE_LINUX || JUCE_BSD | |||
| ::Display* display = XWindowSystem::getInstance()->getDisplay(); | |||
| Window pluginWindow = 0; | |||
| #endif | |||
| #else | |||
| static constexpr auto nativeScaleFactor = 1.0f; | |||
| #endif | |||
| //============================================================================== | |||
| @@ -3485,6 +3493,14 @@ AudioProcessorEditor* VSTPluginInstance::createEditor() | |||
| #endif | |||
| } | |||
| bool VSTPluginInstance::updateSizeFromEditor (int w, int h) | |||
| { | |||
| if (auto* editor = dynamic_cast<VSTPluginWindow*> (getActiveEditor())) | |||
| return editor->updateSizeFromEditor (w, h); | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| // entry point for all callbacks from the plugin | |||
| static pointer_sized_int VSTCALLBACK audioMaster (Vst2::AEffect* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) | |||
| @@ -77,6 +77,26 @@ static bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||
| #endif | |||
| template <typename Callback> | |||
| void callOnMessageThread (Callback&& callback) | |||
| { | |||
| if (MessageManager::getInstance()->existsAndIsLockedByCurrentThread()) | |||
| { | |||
| callback(); | |||
| return; | |||
| } | |||
| WaitableEvent completionEvent; | |||
| MessageManager::callAsync ([&callback, &completionEvent] | |||
| { | |||
| callback(); | |||
| completionEvent.signal(); | |||
| }); | |||
| completionEvent.wait(); | |||
| } | |||
| #if JUCE_MAC | |||
| //============================================================================== | |||
| @@ -203,6 +223,7 @@ private: | |||
| #include "utilities/juce_ParameterAttachments.cpp" | |||
| #include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
| #include "utilities/juce_PluginHostType.cpp" | |||
| #include "utilities/juce_NativeScaleFactorNotifier.cpp" | |||
| #if JUCE_UNIT_TESTS | |||
| #include "format_types/juce_VST3PluginFormat_test.cpp" | |||
| @@ -111,6 +111,7 @@ | |||
| //============================================================================== | |||
| #include "utilities/juce_VSTCallbackHandler.h" | |||
| #include "utilities/juce_VST3ClientExtensions.h" | |||
| #include "utilities/juce_NativeScaleFactorNotifier.h" | |||
| #include "utilities/juce_ExtensionsVisitor.h" | |||
| #include "processors/juce_AudioProcessorParameter.h" | |||
| #include "processors/juce_HostedAudioProcessorParameter.h" | |||
| @@ -0,0 +1,59 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 7 technical preview. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For the technical preview this file cannot be licensed commercially. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| static void removeScaleFactorListenerFromAllPeers (ComponentPeer::ScaleFactorListener& listener) | |||
| { | |||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||
| ComponentPeer::getPeer (i)->removeScaleFactorListener (&listener); | |||
| } | |||
| NativeScaleFactorNotifier::NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged) | |||
| : ComponentMovementWatcher (comp), | |||
| scaleChanged (std::move (onScaleChanged)) | |||
| { | |||
| componentPeerChanged(); | |||
| } | |||
| NativeScaleFactorNotifier::~NativeScaleFactorNotifier() | |||
| { | |||
| removeScaleFactorListenerFromAllPeers (*this); | |||
| } | |||
| void NativeScaleFactorNotifier::nativeScaleFactorChanged (double newScaleFactor) | |||
| { | |||
| NullCheckedInvocation::invoke (scaleChanged, (float) newScaleFactor); | |||
| } | |||
| void NativeScaleFactorNotifier::componentPeerChanged() | |||
| { | |||
| removeScaleFactorListenerFromAllPeers (*this); | |||
| if (auto* x = getComponent()) | |||
| peer = x->getPeer(); | |||
| if (auto* x = peer) | |||
| { | |||
| x->addScaleFactorListener (this); | |||
| nativeScaleFactorChanged (x->getPlatformScaleFactor()); | |||
| } | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,60 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE 7 technical preview. | |||
| Copyright (c) 2022 - Raw Material Software Limited | |||
| You may use this code under the terms of the GPL v3 | |||
| (see www.gnu.org/licenses). | |||
| For the technical preview this file cannot be licensed commercially. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| /** | |||
| Calls a function every time the native scale factor of a component's peer changes. | |||
| This is used in the VST and VST3 wrappers to ensure that the editor's scale is kept in sync with | |||
| the scale of its containing component. | |||
| */ | |||
| class NativeScaleFactorNotifier : private ComponentMovementWatcher, | |||
| private ComponentPeer::ScaleFactorListener | |||
| { | |||
| public: | |||
| /** Constructs an instance. | |||
| While the instance is alive, it will listen for changes to the scale factor of the | |||
| comp's peer, and will call onScaleChanged whenever this scale factor changes. | |||
| @param comp The component to observe | |||
| @param onScaleChanged A function that will be called when the backing scale factor changes | |||
| */ | |||
| NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged); | |||
| ~NativeScaleFactorNotifier() override; | |||
| private: | |||
| void nativeScaleFactorChanged (double newScaleFactor) override; | |||
| void componentPeerChanged() override; | |||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||
| void componentVisibilityChanged() override {} | |||
| using ComponentMovementWatcher::componentMovedOrResized; | |||
| void componentMovedOrResized (bool, bool) override {} | |||
| ComponentPeer* peer = nullptr; | |||
| std::function<void (float)> scaleChanged; | |||
| JUCE_DECLARE_NON_COPYABLE (NativeScaleFactorNotifier) | |||
| JUCE_DECLARE_NON_MOVEABLE (NativeScaleFactorNotifier) | |||
| }; | |||
| } // namespace juce | |||
| @@ -465,17 +465,18 @@ static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) | |||
| #endif | |||
| } | |||
| static bool isPerMonitorDPIAwareThread() | |||
| static bool isPerMonitorDPIAwareThread (GetThreadDPIAwarenessContextFunc getThreadDPIAwarenessContextIn = getThreadDPIAwarenessContext, | |||
| GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContextIn = getAwarenessFromDPIAwarenessContext) | |||
| { | |||
| #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
| return false; | |||
| #else | |||
| setDPIAwareness(); | |||
| if (getThreadDPIAwarenessContext != nullptr | |||
| && getAwarenessFromDPIAwarenessContext != nullptr) | |||
| if (getThreadDPIAwarenessContextIn != nullptr | |||
| && getAwarenessFromDPIAwarenessContextIn != nullptr) | |||
| { | |||
| return (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) | |||
| return (getAwarenessFromDPIAwarenessContextIn (getThreadDPIAwarenessContextIn()) | |||
| == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | |||
| } | |||
| @@ -569,12 +570,17 @@ ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() = default; | |||
| ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() | |||
| { | |||
| if (! isPerMonitorDPIAwareThread()) | |||
| static auto localGetThreadDpiAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); | |||
| static auto localGetAwarenessFromDpiAwarenessContextFunc = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); | |||
| if (! isPerMonitorDPIAwareThread (localGetThreadDpiAwarenessContext, localGetAwarenessFromDpiAwarenessContextFunc)) | |||
| return; | |||
| if (setThreadDPIAwarenessContext != nullptr) | |||
| static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||
| if (localSetThreadDPIAwarenessContext != nullptr) | |||
| { | |||
| previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||
| previousContext = localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||
| #if JUCE_DEBUG | |||
| ++numActiveScopedDpiAwarenessDisablers; | |||
| @@ -586,7 +592,10 @@ ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() | |||
| { | |||
| if (previousContext != nullptr) | |||
| { | |||
| setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); | |||
| static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||
| if (localSetThreadDPIAwarenessContext != nullptr) | |||
| localSetThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); | |||
| #if JUCE_DEBUG | |||
| --numActiveScopedDpiAwarenessDisablers; | |||
| @@ -64,6 +64,9 @@ public: | |||
| /** Resizes this component to fit the HWND that it contains. */ | |||
| void resizeToFit(); | |||
| /** Forces the bounds of the HWND to match the bounds of this component. */ | |||
| void updateHWNDBounds(); | |||
| /** @internal */ | |||
| void paint (Graphics&) override; | |||
| @@ -610,7 +610,7 @@ private: | |||
| if (auto* peer = owner.getPeer()) | |||
| { | |||
| auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds()); | |||
| return r * peer->getPlatformScaleFactor(); | |||
| return r * peer->getPlatformScaleFactor() * peer->getComponent().getDesktopScaleFactor(); | |||
| } | |||
| return owner.getLocalBounds(); | |||
| @@ -165,4 +165,10 @@ void HWNDComponent::resizeToFit() | |||
| setBounds (pimpl->getHWNDBounds()); | |||
| } | |||
| void HWNDComponent::updateHWNDBounds() | |||
| { | |||
| if (pimpl != nullptr) | |||
| pimpl->componentMovedOrResized (true, true); | |||
| } | |||
| } // namespace juce | |||