From 45409bb4e62fa583b8e4973cdd72f479af814174 Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 26 Feb 2021 23:42:47 +0000 Subject: [PATCH 01/17] FileChooser: Hide chooser when it leaves scope on Windows --- extras/Build/juceaide/Main.cpp | 2 +- .../components/juce_Component.h | 5 +- .../filebrowser/juce_FileChooser.cpp | 12 +- .../filebrowser/juce_FileChooser.h | 7 +- .../native/juce_android_FileChooser.cpp | 6 +- .../native/juce_ios_FileChooser.mm | 6 +- .../native/juce_linux_FileChooser.cpp | 4 +- .../native/juce_mac_FileChooser.mm | 6 +- .../native/juce_win32_FileChooser.cpp | 150 ++++++++++-------- 9 files changed, 104 insertions(+), 94 deletions(-) mode change 100644 => 100755 modules/juce_gui_basics/native/juce_win32_FileChooser.cpp diff --git a/extras/Build/juceaide/Main.cpp b/extras/Build/juceaide/Main.cpp index a2e2fc579e..25b4efc50f 100644 --- a/extras/Build/juceaide/Main.cpp +++ b/extras/Build/juceaide/Main.cpp @@ -495,7 +495,7 @@ int main (int argc, char** argv) std::transform (argv, argv + argc, std::back_inserter (arguments), getString); juce::ArgumentList argumentList { arguments.front(), - juce::StringArray (arguments.data() + 1, arguments.size() - 1) }; + juce::StringArray (arguments.data() + 1, (int) arguments.size() - 1) }; using Fn = typename std::add_lvalue_reference::type; diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h index 6b2b0072b7..69dfcf11b4 100644 --- a/modules/juce_gui_basics/components/juce_Component.h +++ b/modules/juce_gui_basics/components/juce_Component.h @@ -2179,10 +2179,7 @@ public: operator ComponentType*() const noexcept { return getComponent(); } /** Returns the component that this pointer refers to, or null if the component no longer exists. */ - ComponentType* operator->() noexcept { return getComponent(); } - - /** Returns the component that this pointer refers to, or null if the component no longer exists. */ - const ComponentType* operator->() const noexcept { return getComponent(); } + ComponentType* operator->() const noexcept { return getComponent(); } /** If the component is valid, this deletes it and sets this pointer to null. */ void deleteAndZero() { delete getComponent(); } diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index 661c02a357..654be39835 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -158,7 +158,7 @@ bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previ { FocusRestorer focusRestorer; - pimpl.reset (createPimpl (flags, previewComp)); + pimpl = createPimpl (flags, previewComp); pimpl->runModally(); // ensure that the finished function was invoked @@ -179,12 +179,12 @@ void FileChooser::launchAsync (int flags, std::functionlaunch(); } -FileChooser::Pimpl* FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) +std::unique_ptr FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) { results.clear(); @@ -214,10 +214,8 @@ FileChooser::Pimpl* FileChooser::createPimpl (int flags, FilePreviewComponent* p { return showPlatformDialog (*this, flags, previewComp); } - else - { - return new NonNative (*this, flags, previewComp); - } + + return std::make_unique (*this, flags, previewComp); } Array FileChooser::getResults() const noexcept diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index e1a50ee98d..c2b25f85aa 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -325,12 +325,11 @@ private: virtual void runModally() = 0; }; - std::unique_ptr pimpl; + std::shared_ptr pimpl; //============================================================================== - Pimpl* createPimpl (int, FilePreviewComponent*); - static Pimpl* showPlatformDialog (FileChooser&, int, - FilePreviewComponent*); + std::unique_ptr createPimpl (int, FilePreviewComponent*); + static std::unique_ptr showPlatformDialog (FileChooser&, int, FilePreviewComponent*); class NonNative; friend class NonNative; diff --git a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp index 35d7f60e02..8d26cac2c0 100644 --- a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp @@ -219,11 +219,11 @@ private: FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; -FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent*) +std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, + FilePreviewComponent*) { if (FileChooser::Native::currentFileChooser == nullptr) - return new FileChooser::Native (owner, flags); + return std::make_unique (owner, flags); // there can only be one file chooser on Android at a once jassertfalse; diff --git a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm index 9d56b55373..7a45fb90ab 100644 --- a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm @@ -379,10 +379,10 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent*) +std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, + FilePreviewComponent*) { - return new FileChooser::Native (owner, flags); + return std::make_unique (owner, flags); } #if JUCE_DEPRECATION_IGNORED diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index b799ee2e4c..6ead1dfe76 100644 --- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -256,9 +256,9 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) +std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { - return new Native (owner, flags); + return std::make_unique (owner, flags); } } // namespace juce diff --git a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index af5d96af71..6b980e335d 100644 --- a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -377,10 +377,10 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) }; -FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent* preview) +std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, + FilePreviewComponent* preview) { - return new FileChooser::Native (owner, flags, preview); + return std::make_unique (owner, flags, preview); } bool FileChooser::isPlatformDialogAvailable() diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp old mode 100644 new mode 100755 index 0c1138a2b0..26bfdc51fc --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -26,32 +26,23 @@ namespace juce { -// Win32NativeFileChooser needs to be a reference counted object as there -// is no way for the parent to know when the dialog HWND has actually been -// created without pumping the message thread (which is forbidden when modal -// loops are disabled). However, the HWND pointer is the only way to cancel -// the dialog box. This means that the actual native FileChooser HWND may -// not have been created yet when the user deletes JUCE's FileChooser class. If this -// occurs the Win32NativeFileChooser will still have a reference count of 1 and will -// simply delete itself immediately once the HWND will have been created a while later. -class Win32NativeFileChooser : public ReferenceCountedObject, +class Win32NativeFileChooser : public std::enable_shared_from_this, private Thread { public: - using Ptr = ReferenceCountedObjectPtr; - enum { charsAvailableForResult = 32768 }; Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp, const File& startingFile, const String& titleToUse, const String& filtersToUse) : Thread ("Native Win32 FileChooser"), - owner (parent), title (titleToUse), filtersString (filtersToUse.replaceCharacter (',', ';')), + owner (parent), + title (titleToUse), + filtersString (filtersToUse.replaceCharacter (',', ';')), selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0), isSave ((flags & FileBrowserComponent::saveMode) != 0), warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0), - selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0), - nativeDialogRef (nullptr), shouldCancel (0) + selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0) { auto parentDirectory = startingFile.getParentDirectory(); @@ -95,10 +86,7 @@ public: { jassert (! isThreadRunning()); - threadHasReference.reset(); startThread(); - - threadHasReference.wait (-1); } else { @@ -112,7 +100,7 @@ public: ScopedLock lock (deletingDialog); customComponent = nullptr; - shouldCancel.set (1); + shouldCancel = true; if (auto hwnd = nativeDialogRef.get()) EndDialog (hwnd, 0); @@ -151,12 +139,11 @@ private: }; //============================================================================== - Component::SafePointer owner; + const Component::SafePointer owner; String title, filtersString; std::unique_ptr customComponent; String initialPath, returnedString; - WaitableEvent threadHasReference; CriticalSection deletingDialog; bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple; @@ -164,15 +151,15 @@ private: HeapBlock files; HeapBlock filters; - Atomic nativeDialogRef; - Atomic shouldCancel; + Atomic nativeDialogRef { nullptr }; + bool shouldCancel = false; struct FreeLPWSTR { void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } }; - bool showDialog (IFileDialog& dialog, bool async) const + bool showDialog (IFileDialog& dialog, bool async) { FILEOPENDIALOGOPTIONS flags = {}; @@ -235,7 +222,49 @@ private: if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec))) return false; - return dialog.Show (static_cast (async ? nullptr : owner->getWindowHandle())) == S_OK; + struct Events : public ComBaseClassHelper + { + explicit Events (Win32NativeFileChooser& o) : owner (o) {} + + JUCE_COMRESULT OnTypeChange (IFileDialog* d) override + { + HWND hwnd = nullptr; + IUnknown_GetWindow (d, &hwnd); + + ScopedLock lock (owner.deletingDialog); + + if (hwnd != nullptr) + owner.nativeDialogRef = hwnd; + + return owner.shouldCancel ? S_FALSE : S_OK; + } + + JUCE_COMRESULT OnFolderChanging (IFileDialog*, IShellItem*) override { return S_OK; } + JUCE_COMRESULT OnFileOk (IFileDialog*) override { return S_OK; } + JUCE_COMRESULT OnFolderChange (IFileDialog*) override { return S_OK; } + JUCE_COMRESULT OnSelectionChange (IFileDialog*) override { return S_OK; } + JUCE_COMRESULT OnShareViolation (IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) override { return S_OK; } + JUCE_COMRESULT OnOverwrite (IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) override { return S_OK; } + + Win32NativeFileChooser& owner; + }; + + DWORD cookie = 0; + dialog.Advise (new Events { *this }, &cookie); + + { + ScopedLock lock (deletingDialog); + + if (shouldCancel) + return false; + } + + const auto result = dialog.Show (async ? nullptr : static_cast (owner->getWindowHandle())) == S_OK; + + ScopedLock lock (deletingDialog); + nativeDialogRef = nullptr; + + return result; } //============================================================================== @@ -447,33 +476,21 @@ private: void run() override { - // We use a functor rather than a lambda here because - // we want to move ownership of the Ptr into the function - // object, and C++11 doesn't support general lambda capture - struct AsyncCallback - { - AsyncCallback (Ptr p, Array r) - : ptr (std::move (p)), - results (std::move (r)) {} + // IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment + CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED); - void operator()() - { - ptr->results = std::move (results); - - if (ptr->owner != nullptr) - ptr->owner->exitModalState (ptr->results.size() > 0 ? 1 : 0); - } + auto resultsCopy = openDialog (true); + auto safeOwner = owner; + std::weak_ptr weakThis = shared_from_this(); - Ptr ptr; - Array results; - }; - - // as long as the thread is running, don't delete this class - Ptr safeThis (this); - threadHasReference.signal(); + MessageManager::callAsync ([resultsCopy, safeOwner, weakThis] + { + if (auto locked = weakThis.lock()) + locked->results = resultsCopy; - auto r = openDialog (true); - MessageManager::callAsync (AsyncCallback (std::move (safeThis), std::move (r))); + if (safeOwner != nullptr) + safeOwner->exitModalState (resultsCopy.size() > 0 ? 1 : 0); + }); } static HashMap& getNativeDialogList() @@ -482,9 +499,9 @@ private: return dialogs; } - static Win32NativeFileChooser* getNativePointerForDialog (HWND hWnd) + static Win32NativeFileChooser* getNativePointerForDialog (HWND hwnd) { - return getNativeDialogList()[hWnd]; + return getNativeDialogList()[hwnd]; } //============================================================================== @@ -552,7 +569,7 @@ private: ScopedLock lock (deletingDialog); getNativeDialogList().set (hdlg, this); - if (shouldCancel.get() != 0) + if (shouldCancel) { EndDialog (hdlg, 0); } @@ -617,7 +634,7 @@ private: { ScopedLock lock (deletingDialog); - if (customComponent != nullptr && shouldCancel.get() == 0) + if (customComponent != nullptr && ! shouldCancel) { if (FilePreviewComponent* comp = dynamic_cast (customComponent->getChildComponent (0))) { @@ -715,14 +732,15 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser) }; -class FileChooser::Native : public Component, +class FileChooser::Native : public std::enable_shared_from_this, + public Component, public FileChooser::Pimpl { public: Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp) : owner (fileChooser), - nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, - fileChooser.title, fileChooser.filters)) + nativeFileChooser (std::make_shared (this, flags, previewComp, fileChooser.startingFile, + fileChooser.title, fileChooser.filters)) { auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; @@ -739,19 +757,17 @@ public: { exitModalState (0); nativeFileChooser->cancel(); - nativeFileChooser = nullptr; } void launch() override { - SafePointer safeThis (this); + std::weak_ptr safeThis = shared_from_this(); - enterModalState (true, ModalCallbackFunction::create ( - [safeThis] (int) - { - if (safeThis != nullptr) - safeThis->owner.finished (safeThis->nativeFileChooser->results); - })); + enterModalState (true, ModalCallbackFunction::create ([safeThis] (int) + { + if (auto locked = safeThis.lock()) + locked->owner.finished (locked->nativeFileChooser->results); + })); nativeFileChooser->open (true); } @@ -783,7 +799,7 @@ public: private: FileChooser& owner; - Win32NativeFileChooser::Ptr nativeFileChooser; + std::shared_ptr nativeFileChooser; }; //============================================================================== @@ -796,10 +812,10 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent* preview) +std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, + FilePreviewComponent* preview) { - return new FileChooser::Native (owner, flags, preview); + return std::make_unique (owner, flags, preview); } } // namespace juce From 75fae3bf213fa7bc56e69491ad06c2a4ce6d4c8b Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 1 Mar 2021 17:05:47 +0000 Subject: [PATCH 02/17] AU: Fix midi output to use correct packet alignment on ARM --- .../AU/juce_AU_Wrapper.mm | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 1a6879078e..aad5da8f28 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -1755,6 +1755,9 @@ private: Array parameterGroups; //============================================================================== + // According to the docs, this is the maximum size of a MIDIPacketList. + static constexpr UInt32 packetListBytes = 65536; + AudioUnitEvent auEvent; mutable Array presetsArray; CriticalSection incomingMidiLock; @@ -1762,6 +1765,7 @@ private: AudioTimeStamp lastTimeStamp; int totalInChannels, totalOutChannels; HeapBlock pulledSucceeded; + HeapBlock packetList { packetListBytes, 1 }; ThreadLocalValue inParameterChangedCallback; @@ -1858,37 +1862,55 @@ private: void pushMidiOutput (UInt32 nFrames) noexcept { - UInt32 numPackets = 0; - size_t dataSize = 0; + MIDIPacket* end = nullptr; - for (const auto metadata : midiEvents) + const auto init = [&] { - jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); - ignoreUnused (nFrames); + end = MIDIPacketListInit (packetList); + }; - dataSize += (size_t) metadata.numBytes; - ++numPackets; - } - - MIDIPacket* p; - const size_t packetMembersSize = sizeof (MIDIPacket) - sizeof (p->data); // NB: GCC chokes on "sizeof (MidiMessage::data)" - const size_t packetListMembersSize = sizeof (MIDIPacketList) - sizeof (p->data); + const auto send = [&] + { + midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); + }; - HeapBlock packetList; - packetList.malloc (packetListMembersSize + packetMembersSize * numPackets + dataSize, 1); - packetList->numPackets = numPackets; + const auto add = [&] (const MidiMessageMetadata& metadata) + { + end = MIDIPacketListAdd (packetList, + packetListBytes, + end, + static_cast (metadata.samplePosition), + static_cast (metadata.numBytes), + metadata.data); + }; - p = packetList->packet; + init(); for (const auto metadata : midiEvents) { - p->timeStamp = (MIDITimeStamp) metadata.samplePosition; - p->length = (UInt16) metadata.numBytes; - memcpy (p->data, metadata.data, (size_t) metadata.numBytes); - p = MIDIPacketNext (p); + jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); + ignoreUnused (nFrames); + + add (metadata); + + if (end == nullptr) + { + send(); + init(); + add (metadata); + + if (end == nullptr) + { + // If this is hit, the size of this midi packet exceeds the maximum size of + // a MIDIPacketList. Large SysEx messages should be broken up into smaller + // chunks. + jassertfalse; + init(); + } + } } - midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); + send(); } void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) From b1253bfc74ca7cca555514fedd496b4be2b912f8 Mon Sep 17 00:00:00 2001 From: ed Date: Tue, 2 Mar 2021 14:51:46 +0000 Subject: [PATCH 03/17] Added JUCE_DECLARE_NON_MOVEABLE macro --- modules/juce_core/system/juce_PlatformDefs.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h index 43000cab6b..8df78273a4 100644 --- a/modules/juce_core/system/juce_PlatformDefs.h +++ b/modules/juce_core/system/juce_PlatformDefs.h @@ -190,7 +190,8 @@ namespace juce #define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) //============================================================================== -/** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. +/** This is a shorthand macro for deleting a class's copy constructor and + copy assignment operator. For example, instead of @code @@ -218,6 +219,13 @@ namespace juce className (const className&) = delete;\ className& operator= (const className&) = delete; +/** This is a shorthand macro for deleting a class's move constructor and + move assignment operator. +*/ +#define JUCE_DECLARE_NON_MOVEABLE(className) \ + className (className&&) = delete;\ + className& operator= (className&&) = delete; + /** This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for a class. */ From 3a0af69eff840958c53185fd0ff79df226d68e25 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 1 Mar 2021 16:43:01 +0000 Subject: [PATCH 04/17] AudioPluginHost: Added an application-level setting for scaling plug-ins on Windows --- extras/AudioPluginHost/Source/HostStartup.cpp | 113 ++++++++++++++++++ .../Source/Plugins/PluginGraph.cpp | 41 ++++--- .../Source/UI/GraphEditorPanel.cpp | 33 ++--- .../Source/UI/MainHostWindow.cpp | 74 ++++++++---- .../Source/UI/MainHostWindow.h | 36 +++++- .../native/juce_win32_Windowing.cpp | 4 +- .../juce_ScopedDPIAwarenessDisabler.h | 3 - modules/juce_gui_extra/juce_gui_extra.cpp | 6 + 8 files changed, 234 insertions(+), 76 deletions(-) diff --git a/extras/AudioPluginHost/Source/HostStartup.cpp b/extras/AudioPluginHost/Source/HostStartup.cpp index 2aca5df3d7..07a9a19daf 100644 --- a/extras/AudioPluginHost/Source/HostStartup.cpp +++ b/extras/AudioPluginHost/Source/HostStartup.cpp @@ -155,5 +155,118 @@ bool isOnTouchDevice() return isTouch; } +//============================================================================== +static AutoScale autoScaleFromString (StringRef str) +{ + if (str.isEmpty()) return AutoScale::useDefault; + if (str == CharPointer_ASCII { "0" }) return AutoScale::scaled; + if (str == CharPointer_ASCII { "1" }) return AutoScale::unscaled; + + jassertfalse; + return AutoScale::useDefault; +} + +static const char* autoScaleToString (AutoScale autoScale) +{ + if (autoScale == AutoScale::scaled) return "0"; + if (autoScale == AutoScale::unscaled) return "1"; + + return {}; +} + +AutoScale getAutoScaleValueForPlugin (const String& identifier) +{ + if (identifier.isNotEmpty()) + { + auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins")); + plugins.removeEmptyStrings(); + + for (auto& plugin : plugins) + { + auto fromIdentifier = plugin.fromFirstOccurrenceOf (identifier, false, false); + + if (fromIdentifier.isNotEmpty()) + return autoScaleFromString (fromIdentifier.fromFirstOccurrenceOf (":", false, false)); + } + } + + return AutoScale::useDefault; +} + +void setAutoScaleValueForPlugin (const String& identifier, AutoScale s) +{ + auto plugins = StringArray::fromLines (getAppProperties().getUserSettings()->getValue ("autoScalePlugins")); + plugins.removeEmptyStrings(); + + auto index = [identifier, plugins] + { + auto it = std::find_if (plugins.begin(), plugins.end(), + [&] (const String& str) { return str.startsWith (identifier); }); + + return (int) std::distance (plugins.begin(), it); + }(); + + if (s == AutoScale::useDefault && index != plugins.size()) + { + plugins.remove (index); + } + else + { + auto str = identifier + ":" + autoScaleToString (s); + + if (index != plugins.size()) + plugins.getReference (index) = str; + else + plugins.add (str); + } + + getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n")); +} + +bool shouldAutoScalePlugin (const String& identifier) +{ + if (! autoScaleOptionAvailable) + return false; + + const auto scaleValue = getAutoScaleValueForPlugin (identifier); + + return (scaleValue == AutoScale::scaled + || (scaleValue == AutoScale::useDefault + && getAppProperties().getUserSettings()->getBoolValue ("autoScalePluginWindows"))); +} + +void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance, + PopupMenu& menu) +{ + if (pluginInstance == nullptr) + return; + + auto description = pluginInstance->getPluginDescription(); + + if (! description.pluginFormatName.contains ("VST")) + return; + + auto identifier = description.fileOrIdentifier; + + PopupMenu autoScaleMenu; + + autoScaleMenu.addItem ("Default", + true, + getAutoScaleValueForPlugin (identifier) == AutoScale::useDefault, + [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::useDefault); }); + + autoScaleMenu.addItem ("Enabled", + true, + getAutoScaleValueForPlugin (identifier) == AutoScale::scaled, + [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::scaled); }); + + autoScaleMenu.addItem ("Disabled", + true, + getAutoScaleValueForPlugin (identifier) == AutoScale::unscaled, + [identifier] { setAutoScaleValueForPlugin (identifier, AutoScale::unscaled); }); + + menu.addSubMenu ("Auto-scale window", autoScaleMenu); +} + // This kicks the whole thing off.. START_JUCE_APPLICATION (PluginHostApp) diff --git a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp index 0cedbcac32..127405419c 100644 --- a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp +++ b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp @@ -29,6 +29,11 @@ #include "InternalPlugins.h" #include "../UI/GraphEditorPanel.h" +static std::unique_ptr makeDPIAwarenessDisablerForPlugin (StringRef identifier) +{ + return shouldAutoScalePlugin (identifier) ? std::make_unique() + : nullptr; +} //============================================================================== PluginGraph::PluginGraph (AudioPluginFormatManager& fm) @@ -76,10 +81,12 @@ AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name) void PluginGraph::addPlugin (const PluginDescription& desc, Point pos) { + std::shared_ptr dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.fileOrIdentifier); + formatManager.createPluginInstanceAsync (desc, graph.getSampleRate(), graph.getBlockSize(), - [this, pos] (std::unique_ptr instance, const String& error) + [this, pos, dpiDisabler] (std::unique_ptr instance, const String& error) { addPluginCallback (std::move (instance), error, pos); }); @@ -156,18 +163,10 @@ PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false); return nullptr; } - } - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - if (! node->properties["DPIAware"] - && ! node->getProcessor()->getName().contains ("Kontakt")) // Kontakt doesn't behave correctly in DPI unaware mode... - { - ScopedDPIAwarenessDisabler disableDPIAwareness; + auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.fileOrIdentifier); return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); } - #endif - - return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); } return nullptr; @@ -332,9 +331,6 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep e->setAttribute ("uid", (int) node->nodeID.uid); e->setAttribute ("x", node->properties ["x"].toString()); e->setAttribute ("y", node->properties ["y"].toString()); - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - e->setAttribute ("DPIAware", node->properties["DPIAware"].toString()); - #endif for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) { @@ -383,10 +379,16 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) break; } - String errorMessage; + auto createInstance = [this, pd] + { + String errorMessage; + + auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (pd.fileOrIdentifier); + return formatManager.createPluginInstance (pd, graph.getSampleRate(), + graph.getBlockSize(), errorMessage); + }; - if (auto instance = formatManager.createPluginInstance (pd, graph.getSampleRate(), - graph.getBlockSize(), errorMessage)) + if (auto instance = createInstance()) { if (auto* layoutEntity = xml.getChildByName ("LAYOUT")) { @@ -408,11 +410,8 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize()); } - node->properties.set ("x", xml.getDoubleAttribute ("x")); - node->properties.set ("y", xml.getDoubleAttribute ("y")); - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - node->properties.set ("DPIAware", xml.getDoubleAttribute ("DPIAware")); - #endif + node->properties.set ("x", xml.getDoubleAttribute ("x")); + node->properties.set ("y", xml.getDoubleAttribute ("y")); for (int i = 0; i < (int) PluginWindow::Type::numTypes; ++i) { diff --git a/extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp b/extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp index 9591c88ac8..f7a5b30880 100644 --- a/extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp +++ b/extras/AudioPluginHost/Source/UI/GraphEditorPanel.cpp @@ -403,21 +403,14 @@ struct GraphEditorPanel::PluginComponent : public Component, menu->addItem (2, "Disconnect all pins"); menu->addItem (3, "Toggle Bypass"); - if (getProcessor()->hasEditor()) - { - menu->addSeparator(); - menu->addItem (10, "Show plugin GUI"); - menu->addItem (11, "Show all programs"); - menu->addItem (12, "Show all parameters"); - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - auto isTicked = false; - if (auto* node = graph.graph.getNodeForId (pluginID)) - isTicked = node->properties["DPIAware"]; - - menu->addItem (13, "Enable DPI awareness", true, isTicked); - #endif - menu->addItem (14, "Show debug log"); - } + menu->addSeparator(); + menu->addItem (10, "Show plugin GUI"); + menu->addItem (11, "Show all programs"); + menu->addItem (12, "Show all parameters"); + menu->addItem (13, "Show debug log"); + + if (autoScaleOptionAvailable) + addPluginAutoScaleOptionsSubMenu (dynamic_cast (getProcessor()), *menu); menu->addSeparator(); menu->addItem (20, "Configure Audio I/O"); @@ -441,15 +434,7 @@ struct GraphEditorPanel::PluginComponent : public Component, case 10: showWindow (PluginWindow::Type::normal); break; case 11: showWindow (PluginWindow::Type::programs); break; case 12: showWindow (PluginWindow::Type::generic) ; break; - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - case 13: - { - if (auto* node = graph.graph.getNodeForId (pluginID)) - node->properties.set ("DPIAware", ! node->properties ["DPIAware"]); - break; - } - #endif - case 14: showWindow (PluginWindow::Type::debug); break; + case 13: showWindow (PluginWindow::Type::debug); break; case 20: showWindow (PluginWindow::Type::audioIO); break; case 21: testStateSaveLoad(); break; diff --git a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp index dfd4927c85..8695f50601 100644 --- a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp +++ b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp @@ -277,9 +277,9 @@ PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& // "Plugins" menu PopupMenu pluginsMenu; addPluginsToMenu (pluginsMenu); - menu.addSubMenu ("Create plugin", pluginsMenu); + menu.addSubMenu ("Create Plug-in", pluginsMenu); menu.addSeparator(); - menu.addItem (250, "Delete all plugins"); + menu.addItem (250, "Delete All Plug-ins"); } else if (topLevelMenuIndex == 2) { @@ -288,17 +288,20 @@ PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor); PopupMenu sortTypeMenu; - sortTypeMenu.addItem (200, "List plugins in default order", true, pluginSortMethod == KnownPluginList::defaultOrder); - sortTypeMenu.addItem (201, "List plugins in alphabetical order", true, pluginSortMethod == KnownPluginList::sortAlphabetically); - sortTypeMenu.addItem (202, "List plugins by category", true, pluginSortMethod == KnownPluginList::sortByCategory); - sortTypeMenu.addItem (203, "List plugins by manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer); - sortTypeMenu.addItem (204, "List plugins based on the directory structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation); - menu.addSubMenu ("Plugin menu type", sortTypeMenu); + sortTypeMenu.addItem (200, "List Plug-ins in Default Order", true, pluginSortMethod == KnownPluginList::defaultOrder); + sortTypeMenu.addItem (201, "List Plug-ins in Alphabetical Order", true, pluginSortMethod == KnownPluginList::sortAlphabetically); + sortTypeMenu.addItem (202, "List Plug-ins by Category", true, pluginSortMethod == KnownPluginList::sortByCategory); + sortTypeMenu.addItem (203, "List Plug-ins by Manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer); + sortTypeMenu.addItem (204, "List Plug-ins Based on the Directory Structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation); + menu.addSubMenu ("Plug-in Menu Type", sortTypeMenu); menu.addSeparator(); menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings); menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision); + if (autoScaleOptionAvailable) + menu.addCommandItem (&getCommandManager(), CommandIDs::autoScalePluginWindows); + menu.addSeparator(); menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox); } @@ -414,7 +417,8 @@ void MainHostWindow::getAllCommands (Array& commands) CommandIDs::showAudioSettings, CommandIDs::toggleDoublePrecision, CommandIDs::aboutBox, - CommandIDs::allWindowsForward + CommandIDs::allWindowsForward, + CommandIDs::autoScalePluginWindows }; commands.addArray (ids, numElementsInArray (ids)); @@ -451,12 +455,12 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma #endif case CommandIDs::showPluginListEditor: - result.setInfo ("Edit the list of available plug-Ins...", String(), category, 0); + result.setInfo ("Edit the List of Available Plug-ins...", {}, category, 0); result.addDefaultKeypress ('p', ModifierKeys::commandModifier); break; case CommandIDs::showAudioSettings: - result.setInfo ("Change the audio device settings", String(), category, 0); + result.setInfo ("Change the Audio Device Settings", {}, category, 0); result.addDefaultKeypress ('a', ModifierKeys::commandModifier); break; @@ -465,7 +469,7 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma break; case CommandIDs::aboutBox: - result.setInfo ("About...", String(), category, 0); + result.setInfo ("About...", {}, category, 0); break; case CommandIDs::allWindowsForward: @@ -473,6 +477,10 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma result.addDefaultKeypress ('w', ModifierKeys::commandModifier); break; + case CommandIDs::autoScalePluginWindows: + updateAutoScaleMenuItem (result); + break; + default: break; } @@ -518,20 +526,30 @@ bool MainHostWindow::perform (const InvocationInfo& info) case CommandIDs::toggleDoublePrecision: if (auto* props = getAppProperties().getUserSettings()) { - bool newIsDoublePrecision = ! isDoublePrecisionProcessing(); + auto newIsDoublePrecision = ! isDoublePrecisionProcessingEnabled(); props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision)); - { - ApplicationCommandInfo cmdInfo (info.commandID); - updatePrecisionMenuItem (cmdInfo); - menuItemsChanged(); - } + ApplicationCommandInfo cmdInfo (info.commandID); + updatePrecisionMenuItem (cmdInfo); + menuItemsChanged(); if (graphHolder != nullptr) graphHolder->setDoublePrecision (newIsDoublePrecision); } break; + case CommandIDs::autoScalePluginWindows: + if (auto* props = getAppProperties().getUserSettings()) + { + auto newAutoScale = ! isAutoScalePluginWindowsEnabled(); + props->setValue ("autoScalePluginWindows", var (newAutoScale)); + + ApplicationCommandInfo cmdInfo (info.commandID); + updateAutoScaleMenuItem (cmdInfo); + menuItemsChanged(); + } + break; + case CommandIDs::aboutBox: // TODO break; @@ -633,7 +651,7 @@ void MainHostWindow::filesDropped (const StringArray& files, int x, int y) } } -bool MainHostWindow::isDoublePrecisionProcessing() +bool MainHostWindow::isDoublePrecisionProcessingEnabled() { if (auto* props = getAppProperties().getUserSettings()) return props->getBoolValue ("doublePrecisionProcessing", false); @@ -641,8 +659,22 @@ bool MainHostWindow::isDoublePrecisionProcessing() return false; } +bool MainHostWindow::isAutoScalePluginWindowsEnabled() +{ + if (auto* props = getAppProperties().getUserSettings()) + return props->getBoolValue ("autoScalePluginWindows", false); + + return false; +} + void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info) { - info.setInfo ("Double floating point precision rendering", String(), "General", 0); - info.setTicked (isDoublePrecisionProcessing()); + info.setInfo ("Double Floating-Point Precision Rendering", {}, "General", 0); + info.setTicked (isDoublePrecisionProcessingEnabled()); +} + +void MainHostWindow::updateAutoScaleMenuItem (ApplicationCommandInfo& info) +{ + info.setInfo ("Auto-Scale Plug-in Windows", {}, "General", 0); + info.setTicked (isAutoScalePluginWindowsEnabled()); } diff --git a/extras/AudioPluginHost/Source/UI/MainHostWindow.h b/extras/AudioPluginHost/Source/UI/MainHostWindow.h index 313a41bdbc..9cebce02c3 100644 --- a/extras/AudioPluginHost/Source/UI/MainHostWindow.h +++ b/extras/AudioPluginHost/Source/UI/MainHostWindow.h @@ -43,12 +43,34 @@ namespace CommandIDs static const int aboutBox = 0x30300; static const int allWindowsForward = 0x30400; static const int toggleDoublePrecision = 0x30500; + static const int autoScalePluginWindows = 0x30600; } +//============================================================================== ApplicationCommandManager& getCommandManager(); ApplicationProperties& getAppProperties(); bool isOnTouchDevice(); +//============================================================================== +enum class AutoScale +{ + scaled, + unscaled, + useDefault +}; + +constexpr bool autoScaleOptionAvailable = + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + true; + #else + false; + #endif + +AutoScale getAutoScaleValueForPlugin (const String&); +void setAutoScaleValueForPlugin (const String&, AutoScale); +bool shouldAutoScalePlugin (const String&); +void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance*, PopupMenu&); + //============================================================================== class MainHostWindow : public DocumentWindow, public MenuBarModel, @@ -88,12 +110,18 @@ public: void addPluginsToMenu (PopupMenu&); PluginDescription getChosenType (int menuID) const; - bool isDoublePrecisionProcessing(); - void updatePrecisionMenuItem (ApplicationCommandInfo& info); - std::unique_ptr graphHolder; private: + //============================================================================== + static bool isDoublePrecisionProcessingEnabled(); + static bool isAutoScalePluginWindowsEnabled(); + + static void updatePrecisionMenuItem (ApplicationCommandInfo& info); + static void updateAutoScaleMenuItem (ApplicationCommandInfo& info); + + void showAudioSettings(); + //============================================================================== AudioDeviceManager deviceManager; AudioPluginFormatManager formatManager; @@ -106,7 +134,5 @@ private: class PluginListWindow; std::unique_ptr pluginListWindow; - void showAudioSettings(); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainHostWindow) }; diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 43eeb6657d..571ae637d7 100755 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -27,7 +27,7 @@ #include #endif -#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra +#if JUCE_MODULE_AVAILABLE_juce_gui_extra #include #endif @@ -470,7 +470,7 @@ static double getGlobalDPI() } //============================================================================== -#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra +#if JUCE_MODULE_AVAILABLE_juce_gui_extra ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { if (! isPerMonitorDPIAwareThread()) diff --git a/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h b/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h index cb9644b782..d3c8893049 100644 --- a/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h +++ b/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h @@ -26,8 +26,6 @@ namespace juce { -#if (JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE) || DOXYGEN - //============================================================================== /** A Windows-specific class that temporarily sets the DPI awareness context of @@ -52,6 +50,5 @@ public: private: void* previousContext = nullptr; }; -#endif } // namespace juce diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index 9cf367411b..9e813c0e7a 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -188,3 +188,9 @@ #include "native/juce_android_WebBrowserComponent.cpp" #endif #endif + +//============================================================================== +#if ! JUCE_WINDOWS + juce::ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); } + juce::ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {} +#endif From f6338c0f8ec372f71f08d93eb991eea18d62a474 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 1 Mar 2021 16:45:07 +0000 Subject: [PATCH 05/17] Windows: Added ScopedThreadDPIAwarenessSetter for correctly setting and resetting thread DPI-awareness for methods which interact with an HWND and removed some DPI workarounds --- .../format_types/juce_VSTPluginFormat.cpp | 7 +- .../juce_audio_processors.cpp | 1 + modules/juce_gui_basics/juce_gui_basics.cpp | 1 + modules/juce_gui_basics/juce_gui_basics.h | 4 + ...uce_win32_ScopedThreadDPIAwarenessSetter.h | 43 +++ .../native/juce_win32_Windowing.cpp | 294 ++++++++++-------- .../juce_ScopedDPIAwarenessDisabler.h | 0 modules/juce_gui_extra/juce_gui_extra.cpp | 1 + .../native/juce_win32_HWNDComponent.cpp | 8 +- modules/juce_opengl/juce_opengl.cpp | 1 + .../juce_opengl/native/juce_OpenGL_win32.h | 25 +- 11 files changed, 232 insertions(+), 153 deletions(-) create mode 100644 modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h mode change 100755 => 100644 modules/juce_gui_basics/native/juce_win32_Windowing.cpp mode change 100644 => 100755 modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h mode change 100644 => 100755 modules/juce_gui_extra/juce_gui_extra.cpp diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index 2716d8b707..fcfd9c3d73 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -83,9 +83,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) //============================================================================== namespace juce { -#if JUCE_WINDOWS - extern void setThreadDPIAwarenessForWindow (HWND); -#endif //============================================================================== namespace @@ -2865,7 +2862,7 @@ public: #if JUCE_WINDOWS if (pluginHWND != 0) { - setThreadDPIAwarenessForWindow (pluginHWND); + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; MoveWindow (pluginHWND, pos.getX(), pos.getY(), roundToInt (getWidth() * nativeScaleFactor), @@ -3128,7 +3125,7 @@ private: // very dodgy logic to decide which size is right. if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) { - setThreadDPIAwarenessForWindow (pluginHWND); + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; SetWindowPos (pluginHWND, 0, 0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp index 43ad88e1b4..36664e7cc5 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -35,6 +35,7 @@ #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 +#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 #include "juce_audio_processors.h" #include diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index c70541c991..12a815f6ba 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -41,6 +41,7 @@ #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 #define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 +#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 #include "juce_gui_basics.h" diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index 615d8f630f..f323d6beba 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -342,6 +342,10 @@ namespace juce #endif #endif +#if JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER && JUCE_WINDOWS + #include "native/juce_win32_ScopedThreadDPIAwarenessSetter.h" +#endif + #include "layout/juce_FlexItem.h" #include "layout/juce_FlexBox.h" diff --git a/modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h b/modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h new file mode 100644 index 0000000000..c8990a7465 --- /dev/null +++ b/modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h @@ -0,0 +1,43 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). + + End User License Agreement: www.juce.com/juce-6-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + 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 +{ + +//============================================================================== +class ScopedThreadDPIAwarenessSetter +{ +public: + explicit ScopedThreadDPIAwarenessSetter (void* nativeWindow); + ~ScopedThreadDPIAwarenessSetter(); + +private: + class NativeImpl; + std::unique_ptr pimpl; + + JUCE_LEAK_DETECTOR (ScopedThreadDPIAwarenessSetter) +}; + +} // namespace juce diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp old mode 100755 new mode 100644 index 571ae637d7..7ac305c878 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -63,6 +63,12 @@ static bool shouldDeactivateTitleBar = true; void* getUser32Function (const char*); +#if JUCE_DEBUG + int numActiveScopedDpiAwarenessDisablers = 0; + bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; } + extern HWND juce_messageWindowHandle; +#endif + //============================================================================== #ifndef WM_TOUCH enum @@ -408,7 +414,10 @@ static void setDPIAwareness() static bool isPerMonitorDPIAwareProcess() { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + return false; + #endif + static bool dpiAware = []() -> bool { setDPIAwareness(); @@ -423,40 +432,43 @@ static bool isPerMonitorDPIAwareProcess() }(); return dpiAware; - #else - return false; - #endif } -static bool isPerMonitorDPIAwareWindow (HWND h) +static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - jassert (h != nullptr); + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + ignoreUnused (h); + return false; + #endif setDPIAwareness(); - if (getWindowDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) - return getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (h)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; + if (getWindowDPIAwarenessContext != nullptr + && getAwarenessFromDPIAwarenessContext != nullptr) + { + return (getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (nativeWindow)) + == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); + } return isPerMonitorDPIAwareProcess(); - #else - ignoreUnused (h); - return false; - #endif } static bool isPerMonitorDPIAwareThread() { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + return false; + #endif + setDPIAwareness(); - if (getThreadDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) - return getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; + if (getThreadDPIAwarenessContext != nullptr + && getAwarenessFromDPIAwarenessContext != nullptr) + { + return (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) + == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); + } return isPerMonitorDPIAwareProcess(); - #else - return false; - #endif } static double getGlobalDPI() @@ -470,6 +482,83 @@ static double getGlobalDPI() } //============================================================================== +class ScopedThreadDPIAwarenessSetter::NativeImpl +{ +public: + explicit NativeImpl (HWND nativeWindow) + { + ignoreUnused (nativeWindow); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (auto* functionSingleton = FunctionSingleton::getInstance()) + { + if (! functionSingleton->isLoaded()) + return; + + auto dpiAwareWindow = (functionSingleton->getAwarenessFromContext (functionSingleton->getWindowAwareness (nativeWindow)) + == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); + + auto dpiAwareThread = (functionSingleton->getAwarenessFromContext (functionSingleton->getThreadAwareness()) + == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); + + if (dpiAwareWindow && ! dpiAwareThread) + oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + else if (! dpiAwareWindow && dpiAwareThread) + oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_UNAWARE); + } + #endif + } + + ~NativeImpl() + { + if (oldContext != nullptr) + if (auto* functionSingleton = FunctionSingleton::getInstance()) + functionSingleton->setThreadAwareness (oldContext); + } + +private: + struct FunctionSingleton : public DeletedAtShutdown + { + FunctionSingleton() = default; + ~FunctionSingleton() override { clearSingletonInstance(); } + + SetThreadDPIAwarenessContextFunc setThreadAwareness = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); + GetWindowDPIAwarenessContextFunc getWindowAwareness = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); + GetThreadDPIAwarenessContextFunc getThreadAwareness = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); + GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); + + bool isLoaded() const noexcept + { + return setThreadAwareness != nullptr + && getWindowAwareness != nullptr + && getThreadAwareness != nullptr + && getAwarenessFromContext != nullptr; + } + + JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FunctionSingleton) + + JUCE_DECLARE_NON_COPYABLE (FunctionSingleton) + JUCE_DECLARE_NON_MOVEABLE (FunctionSingleton) + }; + + DPI_AWARENESS_CONTEXT oldContext = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeImpl) + JUCE_DECLARE_NON_MOVEABLE (NativeImpl) +}; + + +JUCE_IMPLEMENT_SINGLETON (ScopedThreadDPIAwarenessSetter::NativeImpl::FunctionSingleton) + +ScopedThreadDPIAwarenessSetter::ScopedThreadDPIAwarenessSetter (void* nativeWindow) +{ + pimpl = std::make_unique ((HWND) nativeWindow); +} + +ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() +{ +} + #if JUCE_MODULE_AVAILABLE_juce_gui_extra ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { @@ -477,13 +566,25 @@ static double getGlobalDPI() return; if (setThreadDPIAwarenessContext != nullptr) + { previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); + + #if JUCE_DEBUG + ++numActiveScopedDpiAwarenessDisablers; + #endif + } } ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() { if (previousContext != nullptr) + { setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); + + #if JUCE_DEBUG + --numActiveScopedDpiAwarenessDisablers; + #endif + } } #endif @@ -527,6 +628,14 @@ static Point convertPhysicalScreenPointToLogical (Point p, HWND h) noe return p; } +static Point convertLogicalScreenPointToPhysical (Point p, HWND h) noexcept +{ + if (isPerMonitorDPIAwareWindow (h)) + return Desktop::getInstance().getDisplays().logicalToPhysical (p, getCurrentDisplayFromScaleFactor (h)); + + return p; +} + JUCE_API double getScaleFactorForWindow (HWND h) { // NB. Using a local function here because we need to call this method from the plug-in wrappers @@ -549,50 +658,11 @@ JUCE_API double getScaleFactorForWindow (HWND h) return 1.0; } -JUCE_API void setThreadDPIAwarenessForWindow (HWND nativeWindow) -{ - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - // NB. Using local functions here because we need to call this method from the plug-in wrappers - // which don't load the DPI-awareness functions on startup - static SetThreadDPIAwarenessContextFunc localSetThreadDPIAwarenessContext = nullptr; - static GetWindowDPIAwarenessContextFunc localGetWindowDPIAwarenessContext = nullptr; - static GetThreadDPIAwarenessContextFunc localGetThreadDPIAwarenessContext = nullptr; - static GetAwarenessFromDpiAwarenessContextFunc localGetAwarenessFromDPIAwarenessContext = nullptr; - - static bool hasChecked = false; - static bool loadedOK = false; - - if (! hasChecked) - { - hasChecked = true; - - localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); - localGetWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); - localGetThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); - localGetAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); - - loadedOK = (localSetThreadDPIAwarenessContext != nullptr && localGetWindowDPIAwarenessContext != nullptr - && localGetThreadDPIAwarenessContext != nullptr && localGetAwarenessFromDPIAwarenessContext != nullptr); - } - - if (loadedOK) - { - auto dpiAwareWindow = localGetAwarenessFromDPIAwarenessContext (localGetWindowDPIAwarenessContext (nativeWindow)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; - auto dpiAwareThread = localGetAwarenessFromDPIAwarenessContext (localGetThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; - - if (dpiAwareWindow && ! dpiAwareThread) - localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); - else if (! dpiAwareWindow && dpiAwareThread) - localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); - } - #else - ignoreUnused (nativeWindow); - #endif -} - //============================================================================== static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags, bool adjustTopLeft = false) { + ScopedThreadDPIAwarenessSetter setter { hwnd }; + if (isPerMonitorDPIAwareWindow (hwnd)) { if (adjustTopLeft) @@ -607,9 +677,7 @@ static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags, bool adj static RECT getWindowScreenRect (HWND hwnd) { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - setThreadDPIAwarenessForWindow (hwnd); - #endif + ScopedThreadDPIAwarenessSetter setter { hwnd }; RECT rect; GetWindowRect (hwnd, &rect); @@ -621,7 +689,10 @@ static RECT getWindowClientRect (HWND hwnd) auto rect = getWindowScreenRect (hwnd); if (auto parentH = GetParent (hwnd)) + { + ScopedThreadDPIAwarenessSetter setter { hwnd }; MapWindowPoints (HWND_DESKTOP, parentH, (LPPOINT) &rect, 2); + } return rect; } @@ -634,14 +705,8 @@ static void setWindowZOrder (HWND hwnd, HWND insertAfter) //============================================================================== double Desktop::getDefaultMasterScale() { - if (! JUCEApplicationBase::isStandaloneApp() - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - || isPerMonitorDPIAwareProcess() - #endif - ) - { + if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess()) return 1.0; - } return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI; } @@ -893,8 +958,8 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) auto hwnd = (HWND) nativeWindowHandle; auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); - const int w = r.getWidth(); - const int h = r.getHeight(); + const auto w = r.getWidth(); + const auto h = r.getHeight(); auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); Image bitmap (nativeBitmap); @@ -1433,10 +1498,8 @@ public: auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd)); - #if JUCE_WIN_PER_MONITOR_DPI_AWARE if (isPerMonitorDPIAwareWindow (hwnd)) return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt(); - #endif return localBounds; }(); @@ -1554,16 +1617,10 @@ public: if (! r.withZeroOrigin().contains (localPos)) return false; - auto globalPos = localPos + getScreenPosition(); + auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(), + hwnd))); - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - if (isPerMonitorDPIAwareThread() || isPerMonitorDPIAwareWindow (hwnd)) - globalPos = Desktop::getInstance().getDisplays().logicalToPhysical (globalPos); - #endif - - auto w = WindowFromPoint (POINTFromPoint (globalPos)); - - return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); + return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); } BorderSize getFrameSize() const override @@ -1663,18 +1720,7 @@ public: void repaint (const Rectangle& area) override { - auto scale = getPlatformScaleFactor(); - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - // if the calling thread is DPI-aware but we are invalidating a non-DPI aware window RECT, we actually have to - // divide the bounds by the scale factor as it will get multiplied for the virtualised paint callback... - if (isPerMonitorDPIAwareThread() && ! isPerMonitorDPIAwareWindow (hwnd)) - scale = 1.0 / Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; - #endif - - auto scaled = area.toDouble() * scale; - auto r = RECTFromRectangle (scaled.getSmallestIntegerContainer()); - + auto r = RECTFromRectangle ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); InvalidateRect (hwnd, &r, FALSE); } @@ -1795,19 +1841,8 @@ public: private: Point getMousePos (POINTL mousePos) const { - auto screenPos = pointFromPOINT ({ mousePos.x, mousePos.y }).toFloat(); - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - auto h = (HWND) peer.getNativeHandle(); - - if (isPerMonitorDPIAwareWindow (h)) - screenPos = convertPhysicalScreenPointToLogical (screenPos.roundToInt(), h).toFloat(); - #else - if (JUCEApplication::isStandaloneApp()) - screenPos /= static_cast (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI); - #endif - - return peer.getComponent().getLocalPoint (nullptr, screenPos); + return peer.getComponent().getLocalPoint (nullptr, convertPhysicalScreenPointToLogical (pointFromPOINT ({ mousePos.x, mousePos.y }), + (HWND) peer.getNativeHandle()).toFloat()); } struct DroppedData @@ -1902,7 +1937,10 @@ public: double getPlatformScaleFactor() const noexcept override { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + return 1.0; + #endif + if (! isPerMonitorDPIAwareWindow (hwnd)) return 1.0; @@ -1916,9 +1954,6 @@ public: } return scaleFactor; - #else - return 1.0; - #endif } private: @@ -2153,6 +2188,14 @@ private: L"", type, 0, 0, 0, 0, parentToAddTo, nullptr, (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr); + #if JUCE_DEBUG + // The DPI-awareness context of this window and JUCE's hidden message window are different. + // You normally want these to match otherwise timer events and async messages will happen + // in a different context to normal HWND messages which can cause issues with UI scaling. + jassert (isPerMonitorDPIAwareWindow (hwnd) == isPerMonitorDPIAwareWindow (juce_messageWindowHandle) + || isInScopedDPIAwarenessDisabler()); + #endif + if (hwnd != nullptr) { SetWindowLongPtr (hwnd, 0, 0); @@ -2179,19 +2222,8 @@ private: setDPIAwareness(); - #if JUCE_WIN_PER_MONITOR_DPI_AWARE if (isPerMonitorDPIAwareThread()) - { - auto bounds = component.getBounds(); - - if (bounds.isEmpty()) - scaleFactor = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; - else - scaleFactor = Desktop::getInstance().getDisplays().getDisplayForRect (bounds)->scale; - - scaleFactor /= Desktop::getInstance().getGlobalScaleFactor(); - } - #endif + scaleFactor = getScaleFactorForWindow (hwnd); setMessageFilter(); updateBorderSize(); @@ -3508,20 +3540,18 @@ private: Point getPointFromLocalLParam (LPARAM lParam) noexcept { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE + auto p = pointFromPOINT (getPOINTFromLParam (lParam)); + if (isPerMonitorDPIAwareWindow (hwnd)) { // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the // physical screen position and then convert this to local logical coordinates - auto localPos = getPOINTFromLParam (lParam); auto r = getWindowScreenRect (hwnd); - - return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + localPos.x + roundToInt (windowBorder.getLeft() * scaleFactor), - r.top + localPos.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); + return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor), + r.top + p.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); } - #endif - return { static_cast (GET_X_LPARAM (lParam)), static_cast (GET_Y_LPARAM (lParam)) }; + return p.toFloat(); } Point getCurrentMousePos() noexcept @@ -4431,10 +4461,8 @@ Point MouseInputSource::getCurrentRawMousePosition() auto p = pointFromPOINT (mousePos); - #if JUCE_WIN_PER_MONITOR_DPI_AWARE if (isPerMonitorDPIAwareThread()) p = Desktop::getInstance().getDisplays().physicalToLogical (p); - #endif return p.toFloat(); } diff --git a/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h b/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h old mode 100644 new mode 100755 diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp old mode 100644 new mode 100755 index 9e813c0e7a..5dc0f13a2e --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -39,6 +39,7 @@ #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 #define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 +#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 #ifndef JUCE_PUSH_NOTIFICATIONS #define JUCE_PUSH_NOTIFICATIONS 0 diff --git a/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp b/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp index 905e137840..7ab2dd5613 100644 --- a/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp @@ -26,8 +26,6 @@ namespace juce { -void setThreadDPIAwarenessForWindow (HWND); - class HWNDComponent::Pimpl : public ComponentMovementWatcher { public: @@ -52,13 +50,13 @@ public: { auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer(); - setThreadDPIAwarenessForWindow (hwnd); - UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER; if (! wasMoved) flagsToSend |= SWP_NOMOVE; if (! wasResized) flagsToSend |= SWP_NOSIZE; + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; + SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend); } } @@ -101,7 +99,7 @@ public: { if (auto* peer = owner.getPeer()) { - setThreadDPIAwarenessForWindow (hwnd); + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { hwnd }; RECT r; GetWindowRect (hwnd, &r); diff --git a/modules/juce_opengl/juce_opengl.cpp b/modules/juce_opengl/juce_opengl.cpp index 76754ae0f5..b6b4e6daeb 100644 --- a/modules/juce_opengl/juce_opengl.cpp +++ b/modules/juce_opengl/juce_opengl.cpp @@ -37,6 +37,7 @@ #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 #define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 +#define JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER 1 #include "juce_opengl.h" diff --git a/modules/juce_opengl/native/juce_OpenGL_win32.h b/modules/juce_opengl/native/juce_OpenGL_win32.h index 6106fce546..d8c892ce82 100644 --- a/modules/juce_opengl/native/juce_OpenGL_win32.h +++ b/modules/juce_opengl/native/juce_OpenGL_win32.h @@ -28,10 +28,6 @@ namespace juce extern ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component&, void* parent); -#if JUCE_WIN_PER_MONITOR_DPI_AWARE - extern void setThreadDPIAwarenessForWindow (HWND); -#endif - //============================================================================== class OpenGLContext::NativeContext #if JUCE_WIN_PER_MONITOR_DPI_AWARE @@ -97,15 +93,17 @@ public: bool initialiseOnRenderThread (OpenGLContext& c) { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - setThreadDPIAwarenessForWindow ((HWND) nativeWindow->getNativeHandle()); - #endif - + threadAwarenessSetter = std::make_unique (nativeWindow->getNativeHandle()); context = &c; return true; } - void shutdownOnRenderThread() { deactivateCurrentContext(); context = nullptr; } + void shutdownOnRenderThread() + { + deactivateCurrentContext(); + context = nullptr; + threadAwarenessSetter = nullptr; + } static void deactivateCurrentContext() { wglMakeCurrent (nullptr, nullptr); } bool makeActive() const noexcept { return isActive() || wglMakeCurrent (dc, renderContext) != FALSE; } @@ -170,6 +168,7 @@ private: std::unique_ptr dummyComponent; std::unique_ptr nativeWindow; + std::unique_ptr threadAwarenessSetter; HGLRC renderContext; HDC dc; OpenGLContext* context = {}; @@ -219,7 +218,13 @@ private: void createNativeWindow (Component& component) { auto* topComp = component.getTopLevelComponent(); - nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, topComp->getWindowHandle())); + + { + auto* parentHWND = topComp->getWindowHandle(); + + ScopedThreadDPIAwarenessSetter setter { parentHWND }; + nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, parentHWND)); + } if (auto* peer = topComp->getPeer()) { From 1ff7fc38b1782c86009c1d1edb8e31669afd4860 Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 1 Mar 2021 16:46:07 +0000 Subject: [PATCH 06/17] Windows: Added ScopedDeviceContext helper --- .../native/juce_win32_Windowing.cpp | 158 ++++++++++-------- 1 file changed, 84 insertions(+), 74 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 7ac305c878..b5dd25e685 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -69,6 +69,25 @@ void* getUser32Function (const char*); extern HWND juce_messageWindowHandle; #endif +struct ScopedDeviceContext +{ + explicit ScopedDeviceContext (HWND h) + : hwnd (h), dc (GetDC (hwnd)) + { + } + + ~ScopedDeviceContext() + { + ReleaseDC (hwnd, dc); + } + + HWND hwnd; + HDC dc; + + JUCE_DECLARE_NON_COPYABLE (ScopedDeviceContext) + JUCE_DECLARE_NON_MOVEABLE (ScopedDeviceContext) +}; + //============================================================================== #ifndef WM_TOUCH enum @@ -475,10 +494,8 @@ static double getGlobalDPI() { setDPIAwareness(); - HDC dc = GetDC (nullptr); - auto dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0; - ReleaseDC (nullptr, dc); - return dpi; + ScopedDeviceContext deviceContext { nullptr }; + return (GetDeviceCaps (deviceContext.dc, LOGPIXELSX) + GetDeviceCaps (deviceContext.dc, LOGPIXELSY)) / 2.0; } //============================================================================== @@ -847,9 +864,10 @@ public: bitmapInfo.bV4V4Compression = BI_RGB; } - HDC dc = GetDC (nullptr); - hdc = CreateCompatibleDC (dc); - ReleaseDC (nullptr, dc); + { + ScopedDeviceContext deviceContext { nullptr }; + hdc = CreateCompatibleDC (deviceContext.dc); + } SetMapMode (hdc, MM_TEXT); @@ -942,10 +960,8 @@ public: private: static bool isGraphicsCard32Bit() { - auto dc = GetDC (nullptr); - auto bitsPerPixel = GetDeviceCaps (dc, BITSPIXEL); - ReleaseDC (nullptr, dc); - return bitsPerPixel > 24; + ScopedDeviceContext deviceContext { nullptr }; + return GetDeviceCaps (deviceContext.dc, BITSPIXEL) > 24; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage) @@ -964,7 +980,7 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true); Image bitmap (nativeBitmap); - HDC dc = GetDC (hwnd); + ScopedDeviceContext deviceContext { hwnd }; if (isPerMonitorDPIAwareProcess()) { @@ -973,18 +989,16 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) SetBrushOrgEx (nativeBitmap->hdc, 0, 0, NULL); StretchBlt (nativeBitmap->hdc, 0, 0, w, h, - dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale), + deviceContext.dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale), SRCCOPY); SetStretchBltMode (nativeBitmap->hdc, prevStretchMode); } else { - BitBlt (nativeBitmap->hdc, 0, 0, w, h, dc, 0, 0, SRCCOPY); + BitBlt (nativeBitmap->hdc, 0, 0, w, h, deviceContext.dc, 0, 0, SRCCOPY); } - ReleaseDC (hwnd, dc); - return SoftwareImageType().convert (bitmap); } @@ -1025,79 +1039,75 @@ namespace IconConverters && bm.bmWidth > 0 && bm.bmHeight > 0)) return {}; - if (auto* tempDC = ::GetDC (nullptr)) + ScopedDeviceContext deviceContext { nullptr }; + + if (auto* dc = ::CreateCompatibleDC (deviceContext.dc)) { - if (auto* dc = ::CreateCompatibleDC (tempDC)) + BITMAPV5HEADER header = {}; + header.bV5Size = sizeof (BITMAPV5HEADER); + header.bV5Width = bm.bmWidth; + header.bV5Height = -bm.bmHeight; + header.bV5Planes = 1; + header.bV5Compression = BI_RGB; + header.bV5BitCount = 32; + header.bV5RedMask = 0x00FF0000; + header.bV5GreenMask = 0x0000FF00; + header.bV5BlueMask = 0x000000FF; + header.bV5AlphaMask = 0xFF000000; + header.bV5CSType = LCS_WINDOWS_COLOR_SPACE; + header.bV5Intent = LCS_GM_IMAGES; + + uint32* bitmapImageData = nullptr; + + if (auto* dib = ::CreateDIBSection (deviceContext.dc, (BITMAPINFO*) &header, DIB_RGB_COLORS, + (void**) &bitmapImageData, nullptr, 0)) { - BITMAPV5HEADER header = {}; - header.bV5Size = sizeof (BITMAPV5HEADER); - header.bV5Width = bm.bmWidth; - header.bV5Height = -bm.bmHeight; - header.bV5Planes = 1; - header.bV5Compression = BI_RGB; - header.bV5BitCount = 32; - header.bV5RedMask = 0x00FF0000; - header.bV5GreenMask = 0x0000FF00; - header.bV5BlueMask = 0x000000FF; - header.bV5AlphaMask = 0xFF000000; - header.bV5CSType = LCS_WINDOWS_COLOR_SPACE; - header.bV5Intent = LCS_GM_IMAGES; - - uint32* bitmapImageData = nullptr; - - if (auto* dib = ::CreateDIBSection (tempDC, (BITMAPINFO*) &header, DIB_RGB_COLORS, - (void**) &bitmapImageData, nullptr, 0)) - { - auto oldObject = ::SelectObject (dc, dib); + auto oldObject = ::SelectObject (dc, dib); - auto numPixels = bm.bmWidth * bm.bmHeight; - auto numColourComponents = (size_t) numPixels * 4; + auto numPixels = bm.bmWidth * bm.bmHeight; + auto numColourComponents = (size_t) numPixels * 4; - // Windows icon data comes as two layers, an XOR mask which contains the bulk - // of the image data and an AND mask which provides the transparency. Annoyingly - // the XOR mask can also contain an alpha channel, in which case the transparency - // mask should not be applied, but there's no way to find out a priori if the XOR - // mask contains an alpha channel. + // Windows icon data comes as two layers, an XOR mask which contains the bulk + // of the image data and an AND mask which provides the transparency. Annoyingly + // the XOR mask can also contain an alpha channel, in which case the transparency + // mask should not be applied, but there's no way to find out a priori if the XOR + // mask contains an alpha channel. - HeapBlock opacityMask (numPixels); - memset (bitmapImageData, 0, numColourComponents); - ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK); - - for (int i = 0; i < numPixels; ++i) - opacityMask[i] = (bitmapImageData[i] == 0); + HeapBlock opacityMask (numPixels); + memset (bitmapImageData, 0, numColourComponents); + ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK); - Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true); - Image::BitmapData imageData (result, Image::BitmapData::readWrite); + for (int i = 0; i < numPixels; ++i) + opacityMask[i] = (bitmapImageData[i] == 0); - memset (bitmapImageData, 0, numColourComponents); - ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL); - memcpy (imageData.data, bitmapImageData, numColourComponents); + Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true); + Image::BitmapData imageData (result, Image::BitmapData::readWrite); - auto imageHasAlphaChannel = [&imageData, numPixels]() - { - for (int i = 0; i < numPixels; ++i) - if (imageData.data[i * 4] != 0) - return true; - - return false; - }; + memset (bitmapImageData, 0, numColourComponents); + ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL); + memcpy (imageData.data, bitmapImageData, numColourComponents); - if (! imageHasAlphaChannel()) - for (int i = 0; i < numPixels; ++i) - imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00; + auto imageHasAlphaChannel = [&imageData, numPixels]() + { + for (int i = 0; i < numPixels; ++i) + if (imageData.data[i * 4] != 0) + return true; - ::SelectObject (dc, oldObject); - ::DeleteObject(dib); - ::DeleteDC (dc); - ::ReleaseDC (nullptr, tempDC); + return false; + }; - return result; - } + if (! imageHasAlphaChannel()) + for (int i = 0; i < numPixels; ++i) + imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00; + ::SelectObject (dc, oldObject); + ::DeleteObject (dib); ::DeleteDC (dc); + + return result; } - ::ReleaseDC (nullptr, tempDC); + ::DeleteDC (dc); } return {}; From fd2f866dd1de58344ec4ed27611cfd61643ae303 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 3 Mar 2021 19:21:41 +0000 Subject: [PATCH 07/17] FileChooser: Avoid throwing bad_weak_ptr It seems like shared_from_this may not be enabled when a unique_ptr is assigned to a shared_ptr (although it *should* be enabled when constructing a new shared_ptr from a unique_ptr). Functions that return objects that may need to use shared_from_this now return shared_ptr, just to be safe. Additionally, in some cases, shared_from_this was being called from Thread::run after the last reference to the shared object had been released. We now call shared_from_this during 'open', which will always run on the message thread while at least once reference to the shared object is alive. --- .../filebrowser/juce_FileChooser.cpp | 2 +- .../juce_gui_basics/filebrowser/juce_FileChooser.h | 4 ++-- .../native/juce_android_FileChooser.cpp | 4 ++-- .../juce_gui_basics/native/juce_ios_FileChooser.mm | 4 ++-- .../native/juce_linux_FileChooser.cpp | 4 ++-- .../juce_gui_basics/native/juce_mac_FileChooser.mm | 4 ++-- .../native/juce_win32_FileChooser.cpp | 13 ++++++++----- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index 654be39835..ac79377759 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -184,7 +184,7 @@ void FileChooser::launchAsync (int flags, std::function FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) +std::shared_ptr FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp) { results.clear(); diff --git a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index c2b25f85aa..df6197b510 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -328,8 +328,8 @@ private: std::shared_ptr pimpl; //============================================================================== - std::unique_ptr createPimpl (int, FilePreviewComponent*); - static std::unique_ptr showPlatformDialog (FileChooser&, int, FilePreviewComponent*); + std::shared_ptr createPimpl (int, FilePreviewComponent*); + static std::shared_ptr showPlatformDialog (FileChooser&, int, FilePreviewComponent*); class NonNative; friend class NonNative; diff --git a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp index 8d26cac2c0..fea21a2e2c 100644 --- a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp @@ -219,11 +219,11 @@ private: FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; -std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, +std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { if (FileChooser::Native::currentFileChooser == nullptr) - return std::make_unique (owner, flags); + return std::make_shared (owner, flags); // there can only be one file chooser on Android at a once jassertfalse; diff --git a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm index 7a45fb90ab..d61b73f0be 100644 --- a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm @@ -379,10 +379,10 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, +std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { - return std::make_unique (owner, flags); + return std::make_shared (owner, flags); } #if JUCE_DEPRECATION_IGNORED diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index 6ead1dfe76..746686bf75 100644 --- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -256,9 +256,9 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) +std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { - return std::make_unique (owner, flags); + return std::make_shared (owner, flags); } } // namespace juce diff --git a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 6b980e335d..61febe1b1d 100644 --- a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -377,10 +377,10 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) }; -std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, +std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent* preview) { - return std::make_unique (owner, flags, preview); + return std::make_shared (owner, flags, preview); } bool FileChooser::isPlatformDialogAvailable() diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index 26bfdc51fc..b16b5b9248 100755 --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -82,6 +82,8 @@ public: // the thread should not be running nativeDialogRef.set (nullptr); + weakThis = shared_from_this(); + if (async) { jassert (! isThreadRunning()); @@ -140,6 +142,7 @@ private: //============================================================================== const Component::SafePointer owner; + std::weak_ptr weakThis; String title, filtersString; std::unique_ptr customComponent; String initialPath, returnedString; @@ -481,11 +484,11 @@ private: auto resultsCopy = openDialog (true); auto safeOwner = owner; - std::weak_ptr weakThis = shared_from_this(); + auto weakThisCopy = weakThis; - MessageManager::callAsync ([resultsCopy, safeOwner, weakThis] + MessageManager::callAsync ([resultsCopy, safeOwner, weakThisCopy] { - if (auto locked = weakThis.lock()) + if (auto locked = weakThisCopy.lock()) locked->results = resultsCopy; if (safeOwner != nullptr) @@ -812,10 +815,10 @@ bool FileChooser::isPlatformDialogAvailable() #endif } -std::unique_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, +std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent* preview) { - return std::make_unique (owner, flags, preview); + return std::make_shared (owner, flags, preview); } } // namespace juce From 7a0b17c0d39fbda40df65e7c2e4b917c31bf77c2 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 3 Mar 2021 16:52:10 +0000 Subject: [PATCH 08/17] LinuxComponentPeer: Fix scaling in custom windows created by plugins Previously, things like PopupMenus which were created in their own windows were not being scaled correctly on HiDPI displays on Linux. This patch forces the display scale to 1.0 in plugins, meaning that the transform applied to the main plugin window is the sole source of truth for component scaling in plugins. --- .../native/juce_linux_Windowing.cpp | 22 +++++++++---------- .../native/x11/juce_linux_XWindowSystem.cpp | 6 ++--- .../native/x11/juce_linux_XWindowSystem.h | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index ea597e3347..453abc8417 100644 --- a/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -85,8 +85,8 @@ public: updateScaleFactorFromNewBounds (bounds, false); - auto physicalBounds = (parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds) - : bounds * currentScaleFactor); + auto physicalBounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds) + : bounds * currentScaleFactor; WeakReference deletionChecker (&component); @@ -103,13 +103,16 @@ public: Point getScreenPosition (bool physical) const { - auto parentPosition = XWindowSystem::getInstance()->getParentScreenPosition(); + auto physicalParentPosition = XWindowSystem::getInstance()->getPhysicalParentScreenPosition(); + auto parentPosition = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalParentPosition) + : physicalParentPosition / currentScaleFactor; - auto screenBounds = (parentWindow == 0 ? bounds - : bounds.translated (parentPosition.x, parentPosition.y)); + auto screenBounds = parentWindow == 0 ? bounds + : bounds.translated (parentPosition.x, parentPosition.y); if (physical) - return Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft()); + return parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft()) + : screenBounds.getTopLeft() * currentScaleFactor; return screenBounds.getTopLeft(); } @@ -314,8 +317,8 @@ public: updateScaleFactorFromNewBounds (physicalBounds, true); - bounds = (parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds) - : physicalBounds / currentScaleFactor); + bounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds) + : physicalBounds / currentScaleFactor; } } @@ -433,9 +436,6 @@ private: //============================================================================== void updateScaleFactorFromNewBounds (const Rectangle& newBounds, bool isPhysical) { - if (! JUCEApplicationBase::isStandaloneApp()) - return; - Point translation = (parentWindow != 0 ? getScreenPosition (isPhysical) : Point()); const auto& desktop = Desktop::getInstance(); diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp index b8e6e0c7ed..4d0c06a390 100644 --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp @@ -1799,14 +1799,14 @@ Rectangle XWindowSystem::getWindowBounds (::Window windowH, ::Window parent } else { - parentScreenPosition = Desktop::getInstance().getDisplays().physicalToLogical (Point (rootX, rootY)); + parentScreenPosition = Point (rootX, rootY); } } return { wx, wy, (int) ww, (int) wh }; } -Point XWindowSystem::getParentScreenPosition() const +Point XWindowSystem::getPhysicalParentScreenPosition() const { return parentScreenPosition; } @@ -2424,7 +2424,7 @@ Array XWindowSystem::findDisplays (float masterScale) const + ((static_cast (crtc->height) * 25.4 * 0.5) / static_cast (output->mm_height)); auto scale = DisplayHelpers::getDisplayScale (output->name, d.dpi); - scale = (scale <= 0.1 ? 1.0 : scale); + scale = (scale <= 0.1 || ! JUCEApplicationBase::isStandaloneApp()) ? 1.0 : scale; d.scale = masterScale * scale; diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h index 3f804a2fc6..dda192484d 100644 --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h +++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h @@ -109,7 +109,7 @@ public: BorderSize getBorderSize (::Window) const; Rectangle getWindowBounds (::Window, ::Window parentWindow); - Point getParentScreenPosition() const; + Point getPhysicalParentScreenPosition() const; bool contains (::Window, Point localPos) const; From 6bcf603f2c7a4d604a5dc13f9ef5cc243c009ae3 Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 5 Mar 2021 11:26:22 +0000 Subject: [PATCH 09/17] AppDelegate: Ensure correct lifetime of static objects Arranges declarations of objects with static storage duration to ensure correct lifetimes. --- .../native/juce_mac_MessageManager.mm | 442 +++++++++--------- 1 file changed, 222 insertions(+), 220 deletions(-) diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm index 1cc0b9f8e1..7a07188689 100644 --- a/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/modules/juce_events/native/juce_mac_MessageManager.mm @@ -33,300 +33,302 @@ using MenuTrackingChangedCallback = void (*)(bool); MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; //============================================================================== -struct AppDelegate +struct AppDelegateClass : public ObjCClass { -public: - AppDelegate() + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") { - static AppDelegateClass cls; - delegate = [cls.createInstance() init]; + addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); + addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); + addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); + addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); + addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); + addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); - NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); + addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); + addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); + addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); + addMethod (@selector (dummyMethod), dummyMethod, "v@:"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + addIvar*> ("pushNotificationsDelegate"); + + addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) - name: NSMenuDidBeginTrackingNotification object: nil]; - [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) - name: NSMenuDidEndTrackingNotification object: nil]; + addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); JUCE_END_IGNORE_WARNINGS_GCC_LIKE - if (JUCEApplicationBase::isStandaloneApp()) - { - [NSApp setDelegate: delegate]; + addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); + #endif - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate - selector: @selector (broadcastMessageCallback:) - name: getBroadcastEventName() - object: nil - suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - else - { - [center addObserver: delegate selector: @selector (applicationDidResignActive:) - name: NSApplicationDidResignActiveNotification object: NSApp]; + registerClass(); + } - [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) - name: NSApplicationDidBecomeActiveNotification object: NSApp]; +private: + static void applicationWillFinishLaunching (id self, SEL, NSNotification*) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self + andSelector: @selector (getUrl:withReplyEvent:) + forEventClass: kInternetEventClass + andEventID: kAEGetURL]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } - [center addObserver: delegate selector: @selector (applicationWillUnhide:) - name: NSApplicationWillUnhideNotification object: NSApp]; + #if JUCE_PUSH_NOTIFICATIONS + static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) + { + if (notification.userInfo != nil) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a + // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type + NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (userNotification != nil && userNotification.userInfo != nil) + didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); } } + #endif - ~AppDelegate() + static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) { - [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; - [[NSNotificationCenter defaultCenter] removeObserver: delegate]; - - if (JUCEApplicationBase::isStandaloneApp()) + if (auto* app = JUCEApplicationBase::getInstance()) { - [NSApp setDelegate: nil]; + app->systemRequestedQuit(); - [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate - name: getBroadcastEventName() - object: nil]; + if (! MessageManager::getInstance()->hasStopMessageBeenSent()) + return NSTerminateCancel; } - [delegate release]; + return NSTerminateNow; } - static NSString* getBroadcastEventName() + static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) { - return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); + JUCEApplicationBase::appWillTerminateByForce(); } - MessageQueue messageQueue; - id delegate; - -private: - //============================================================================== - struct AppDelegateClass : public ObjCClass + static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) { - AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") + if (auto* app = JUCEApplicationBase::getInstance()) { - addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); - addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); - addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); - addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); - addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); - addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); - addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); + return YES; + } - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); - addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); - addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); - addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); - addMethod (@selector (dummyMethod), dummyMethod, "v@:"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + return NO; + } - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - addIvar*> ("pushNotificationsDelegate"); + static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + StringArray files; - addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); + for (NSString* f in filenames) + files.add (quotedIfContainsSpaces (f)); - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + if (files.size() > 0) + app->anotherInstanceStarted (files.joinIntoString (" ")); + } + } - addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); - #endif + static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } - registerClass(); - } + static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) + { + NSDictionary* dict = (NSDictionary*) [n userInfo]; + auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); + MessageManager::getInstance()->deliverBroadcastMessage (messageString); + } - private: - static void applicationWillFinishLaunching (id self, SEL, NSNotification*) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self - andSelector: @selector (getUrl:withReplyEvent:) - forEventClass: kInternetEventClass - andEventID: kAEGetURL]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } + static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (true); + } - #if JUCE_PUSH_NOTIFICATIONS - static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) - { - if (notification.userInfo != nil) - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a - // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type - NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - if (userNotification != nil && userNotification.userInfo != nil) - didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); - } - } - #endif + static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (false); + } - static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->systemRequestedQuit(); + static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) - if (! MessageManager::getInstance()->hasStopMessageBeenSent()) - return NSTerminateCancel; - } + static void focusChanged() + { + if (appFocusChangeCallback != nullptr) + (*appFocusChangeCallback)(); + } - return NSTerminateNow; - } + static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) + { + if (auto* app = JUCEApplicationBase::getInstance()) + app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); + } - static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) - { - JUCEApplicationBase::appWillTerminateByForce(); - } + static String quotedIfContainsSpaces (NSString* file) + { + String s (nsStringToJuce (file)); + s = s.unquoted().replace ("\"", "\\\""); - static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); - return YES; - } + if (s.containsChar (' ')) + s = s.quoted(); - return NO; - } + return s; + } - static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - StringArray files; + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) + { + object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); + } - for (NSString* f in filenames) - files.add (quotedIfContainsSpaces (f)); + static NSObject* getPushNotificationsDelegate (id self) + { + return getIvar*> (self, "pushNotificationsDelegate"); + } - if (files.size() > 0) - app->anotherInstanceStarted (files.joinIntoString (" ")); - } - } + static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) + { + auto* delegate = getPushNotificationsDelegate (self); - static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } + SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); - static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) + if (delegate != nil && [delegate respondsToSelector: selector]) { - NSDictionary* dict = (NSDictionary*) [n userInfo]; - auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); - MessageManager::getInstance()->deliverBroadcastMessage (messageString); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &deviceToken atIndex:3]; - static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (true); + [invocation invoke]; } + } - static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (false); - } + static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) + { + auto* delegate = getPushNotificationsDelegate (self); - static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) + SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); - static void focusChanged() + if (delegate != nil && [delegate respondsToSelector: selector]) { - if (appFocusChangeCallback != nullptr) - (*appFocusChangeCallback)(); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &error atIndex:3]; - static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); + [invocation invoke]; } + } - static String quotedIfContainsSpaces (NSString* file) - { - String s (nsStringToJuce (file)); - s = s.unquoted().replace ("\"", "\\\""); + static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) + { + auto* delegate = getPushNotificationsDelegate (self); - if (s.containsChar (' ')) - s = s.quoted(); + SEL selector = @selector (application:didReceiveRemoteNotification:); - return s; - } - - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) + if (delegate != nil && [delegate respondsToSelector: selector]) { - object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); - } + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &userInfo atIndex:3]; - static NSObject* getPushNotificationsDelegate (id self) - { - return getIvar*> (self, "pushNotificationsDelegate"); + [invocation invoke]; } + } + #endif +}; - static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) - { - auto* delegate = getPushNotificationsDelegate (self); +// This is declared at file scope, so that it's guaranteed to be +// constructed before and destructed after `appDelegate` (below) +static AppDelegateClass appDelegateClass; - SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); +//============================================================================== +struct AppDelegate +{ +public: + AppDelegate() + { + delegate = [appDelegateClass.createInstance() init]; - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &deviceToken atIndex:3]; + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - [invocation invoke]; - } - } + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) + name: NSMenuDidBeginTrackingNotification object: nil]; + [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) + name: NSMenuDidEndTrackingNotification object: nil]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE - static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) + if (JUCEApplicationBase::isStandaloneApp()) { - auto* delegate = getPushNotificationsDelegate (self); + [NSApp setDelegate: delegate]; - SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate + selector: @selector (broadcastMessageCallback:) + name: getBroadcastEventName() + object: nil + suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + else + { + [center addObserver: delegate selector: @selector (applicationDidResignActive:) + name: NSApplicationDidResignActiveNotification object: NSApp]; - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &error atIndex:3]; + [center addObserver: delegate selector: @selector (applicationDidBecomeActive:) + name: NSApplicationDidBecomeActiveNotification object: NSApp]; - [invocation invoke]; - } + [center addObserver: delegate selector: @selector (applicationWillUnhide:) + name: NSApplicationWillUnhideNotification object: NSApp]; } + } - static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) + ~AppDelegate() + { + [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; + [[NSNotificationCenter defaultCenter] removeObserver: delegate]; + + if (JUCEApplicationBase::isStandaloneApp()) { - auto* delegate = getPushNotificationsDelegate (self); + [NSApp setDelegate: nil]; - SEL selector = @selector (application:didReceiveRemoteNotification:); + [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate + name: getBroadcastEventName() + object: nil]; + } - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &userInfo atIndex:3]; + [delegate release]; + } - [invocation invoke]; - } - } - #endif - }; + static NSString* getBroadcastEventName() + { + return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); + } + + MessageQueue messageQueue; + id delegate; }; //============================================================================== From 8500f40b953374cc1a7415d9dd7ddd16f0b66896 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:01:45 +0000 Subject: [PATCH 10/17] AudioPluginHost: Only auto-scale VST plug-in windows --- extras/AudioPluginHost/Source/HostStartup.cpp | 14 ++++++++++---- .../AudioPluginHost/Source/Plugins/PluginGraph.cpp | 12 ++++++------ extras/AudioPluginHost/Source/UI/MainHostWindow.h | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/extras/AudioPluginHost/Source/HostStartup.cpp b/extras/AudioPluginHost/Source/HostStartup.cpp index 07a9a19daf..77428db4da 100644 --- a/extras/AudioPluginHost/Source/HostStartup.cpp +++ b/extras/AudioPluginHost/Source/HostStartup.cpp @@ -223,12 +223,18 @@ void setAutoScaleValueForPlugin (const String& identifier, AutoScale s) getAppProperties().getUserSettings()->setValue ("autoScalePlugins", plugins.joinIntoString ("\n")); } -bool shouldAutoScalePlugin (const String& identifier) +static bool isAutoScaleAvailableForPlugin (const PluginDescription& description) { - if (! autoScaleOptionAvailable) + return autoScaleOptionAvailable + && description.pluginFormatName.containsIgnoreCase ("VST"); +} + +bool shouldAutoScalePlugin (const PluginDescription& description) +{ + if (! isAutoScaleAvailableForPlugin (description)) return false; - const auto scaleValue = getAutoScaleValueForPlugin (identifier); + const auto scaleValue = getAutoScaleValueForPlugin (description.fileOrIdentifier); return (scaleValue == AutoScale::scaled || (scaleValue == AutoScale::useDefault @@ -243,7 +249,7 @@ void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance* pluginInstance, auto description = pluginInstance->getPluginDescription(); - if (! description.pluginFormatName.contains ("VST")) + if (! isAutoScaleAvailableForPlugin (description)) return; auto identifier = description.fileOrIdentifier; diff --git a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp index 127405419c..7fe29f1bf3 100644 --- a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp +++ b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp @@ -29,10 +29,10 @@ #include "InternalPlugins.h" #include "../UI/GraphEditorPanel.h" -static std::unique_ptr makeDPIAwarenessDisablerForPlugin (StringRef identifier) +static std::unique_ptr makeDPIAwarenessDisablerForPlugin (const PluginDescription& desc) { - return shouldAutoScalePlugin (identifier) ? std::make_unique() - : nullptr; + return shouldAutoScalePlugin (desc) ? std::make_unique() + : nullptr; } //============================================================================== @@ -81,7 +81,7 @@ AudioProcessorGraph::Node::Ptr PluginGraph::getNodeForName (const String& name) void PluginGraph::addPlugin (const PluginDescription& desc, Point pos) { - std::shared_ptr dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc.fileOrIdentifier); + std::shared_ptr dpiDisabler = makeDPIAwarenessDisablerForPlugin (desc); formatManager.createPluginInstanceAsync (desc, graph.getSampleRate(), @@ -164,7 +164,7 @@ PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node return nullptr; } - auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description.fileOrIdentifier); + auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (description); return activePluginWindows.add (new PluginWindow (node, type, activePluginWindows)); } } @@ -383,7 +383,7 @@ void PluginGraph::createNodeFromXml (const XmlElement& xml) { String errorMessage; - auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (pd.fileOrIdentifier); + auto localDpiDisabler = makeDPIAwarenessDisablerForPlugin (pd); return formatManager.createPluginInstance (pd, graph.getSampleRate(), graph.getBlockSize(), errorMessage); }; diff --git a/extras/AudioPluginHost/Source/UI/MainHostWindow.h b/extras/AudioPluginHost/Source/UI/MainHostWindow.h index 9cebce02c3..1a43449831 100644 --- a/extras/AudioPluginHost/Source/UI/MainHostWindow.h +++ b/extras/AudioPluginHost/Source/UI/MainHostWindow.h @@ -68,7 +68,7 @@ constexpr bool autoScaleOptionAvailable = AutoScale getAutoScaleValueForPlugin (const String&); void setAutoScaleValueForPlugin (const String&, AutoScale); -bool shouldAutoScalePlugin (const String&); +bool shouldAutoScalePlugin (const PluginDescription&); void addPluginAutoScaleOptionsSubMenu (AudioPluginInstance*, PopupMenu&); //============================================================================== From c32142a215f234e8e015edfd9c9ad9da5acdf7fd Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:02:05 +0000 Subject: [PATCH 11/17] Windows: Fixed a build error and some unreachable code warnings when JUCE_WIN_PER_MONITOR_DPI_AWARE=0 --- .../native/juce_win32_Windowing.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index b5dd25e685..f18b890f43 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -435,8 +435,7 @@ static bool isPerMonitorDPIAwareProcess() { #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE return false; - #endif - + #else static bool dpiAware = []() -> bool { setDPIAwareness(); @@ -451,15 +450,15 @@ static bool isPerMonitorDPIAwareProcess() }(); return dpiAware; + #endif } static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) { #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE - ignoreUnused (h); + ignoreUnused (nativeWindow); return false; - #endif - + #else setDPIAwareness(); if (getWindowDPIAwarenessContext != nullptr @@ -470,14 +469,14 @@ static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) } return isPerMonitorDPIAwareProcess(); + #endif } static bool isPerMonitorDPIAwareThread() { #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE return false; - #endif - + #else setDPIAwareness(); if (getThreadDPIAwarenessContext != nullptr @@ -488,6 +487,7 @@ static bool isPerMonitorDPIAwareThread() } return isPerMonitorDPIAwareProcess(); + #endif } static double getGlobalDPI() @@ -1949,8 +1949,7 @@ public: { #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE return 1.0; - #endif - + #else if (! isPerMonitorDPIAwareWindow (hwnd)) return 1.0; @@ -1964,6 +1963,7 @@ public: } return scaleFactor; + #endif } private: From 814044274635109adba7e6cbe2d0b40ab908fe5a Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:02:22 +0000 Subject: [PATCH 12/17] VST: Removed an old workaround causing plug-in editor window position issues on macOS --- modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 58b34493bb..f0f915fa2d 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -1169,11 +1169,6 @@ public: { auto editorBounds = getSizeToContainChild(); - #if JUCE_MAC - if (wrapper.useNSView) - setTopLeftPosition (0, getHeight() - editorBounds.getHeight()); - #endif - resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); { From 7b68d5fa4be18a24718c8c7bd2d54e26f63cb74b Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:03:31 +0000 Subject: [PATCH 13/17] VST: Increased the scoped of a thread DPI awareness setter when creating plug-in editors on Windows --- .../format_types/juce_VSTPluginFormat.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index fcfd9c3d73..bcc1e3d884 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -3110,7 +3110,12 @@ private: JUCE_END_IGNORE_WARNINGS_MSVC RECT r; - GetWindowRect (pluginHWND, &r); + + { + ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; + GetWindowRect (pluginHWND, &r); + } + auto w = (int) (r.right - r.left); auto h = (int) (r.bottom - r.top); From 655a6e93678e9ee6f8f6cd871a2a7ce6aebade05 Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:04:02 +0000 Subject: [PATCH 14/17] VST: Use ComponentPeer::getAreaCoveredBy() to get scaled editor bounds on Windows and Linux --- .../format_types/juce_VSTPluginFormat.cpp | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index bcc1e3d884..647a6b55be 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -2851,38 +2851,30 @@ public: if (recursiveResize) return; - auto* topComp = getTopLevelComponent(); - - if (topComp->getPeer() != nullptr) + if (auto* peer = getTopLevelComponent()->getPeer()) { - auto pos = (topComp->getLocalPoint (this, Point()) * nativeScaleFactor).roundToInt(); + const ScopedValueSetter recursiveResizeSetter (recursiveResize, true); - recursiveResize = true; + auto pos = (peer->getAreaCoveredBy (*this).toFloat() * nativeScaleFactor).toNearestInt(); #if JUCE_WINDOWS if (pluginHWND != 0) { ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; - - MoveWindow (pluginHWND, pos.getX(), pos.getY(), - roundToInt (getWidth() * nativeScaleFactor), - roundToInt (getHeight() * nativeScaleFactor), - TRUE); + MoveWindow (pluginHWND, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), TRUE); } #elif JUCE_LINUX if (pluginWindow != 0) { X11Symbols::getInstance()->xMoveResizeWindow (display, pluginWindow, pos.getX(), pos.getY(), - static_cast (roundToInt ((float) getWidth() * nativeScaleFactor)), - static_cast (roundToInt ((float) getHeight() * nativeScaleFactor))); + (unsigned int) pos.getWidth(), + (unsigned int) pos.getHeight()); X11Symbols::getInstance()->xMapRaised (display, pluginWindow); X11Symbols::getInstance()->xFlush (display); } #endif - - recursiveResize = false; } } From 5a59c92b200c1c42c5e2f34186e1567c57d22d2f Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 5 Mar 2021 18:04:16 +0000 Subject: [PATCH 15/17] AudioProcessorEditor: Allow editors to have a corner resizer without being resizable by the host and clarified the documentation regarding this and the editor's constrainer --- .../processors/juce_AudioProcessorEditor.cpp | 71 +++++++------------ .../processors/juce_AudioProcessorEditor.h | 50 ++++++++----- 2 files changed, 55 insertions(+), 66 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index b980441c94..7db87b2858 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -73,38 +73,24 @@ void AudioProcessorEditor::initialise() // END SECTION A - resizable = false; - - attachConstrainer (&defaultConstrainer); + setConstrainer (&defaultConstrainer); resizeListener.reset (new AudioProcessorEditorListener (*this)); addComponentListener (resizeListener.get()); } //============================================================================== -void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) +void AudioProcessorEditor::setResizable (bool allowHostToResize, bool useBottomRightCornerResizer) { - if (shouldBeResizable != resizable) - { - resizable = shouldBeResizable; - - if (! resizable && constrainer == &defaultConstrainer) - { - auto width = getWidth(); - auto height = getHeight(); - - if (width > 0 && height > 0) - defaultConstrainer.setSizeLimits (width, height, width, height); - } - } + resizableByHost = allowHostToResize; - bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); + const auto hasResizableCorner = (resizableCorner.get() != nullptr); - if (shouldHaveCornerResizer != (resizableCorner != nullptr)) + if (useBottomRightCornerResizer != hasResizableCorner) { - if (shouldHaveCornerResizer) + if (useBottomRightCornerResizer) attachResizableCornerComponent(); else - resizableCorner.reset(); + resizableCorner = nullptr; } } @@ -113,19 +99,23 @@ void AudioProcessorEditor::setResizeLimits (int newMinimumWidth, int newMaximumWidth, int newMaximumHeight) noexcept { - // if you've set up a custom constrainer then these settings won't have any effect.. - jassert (constrainer == &defaultConstrainer || constrainer == nullptr); + if (constrainer != nullptr && constrainer != &defaultConstrainer) + { + // if you've set up a custom constrainer then these settings won't have any effect.. + jassertfalse; + return; + } - const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); - const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); + resizableByHost = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); - setResizable (shouldEnableResize, shouldHaveCornerResizer); + defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, + newMaximumWidth, newMaximumHeight); if (constrainer == nullptr) setConstrainer (&defaultConstrainer); - defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, - newMaximumWidth, newMaximumHeight); + if (resizableCorner != nullptr) + attachResizableCornerComponent(); setBoundsConstrained (getBounds()); } @@ -134,29 +124,21 @@ void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstr { if (constrainer != newConstrainer) { - if (newConstrainer != nullptr) - resizable = (newConstrainer->getMinimumWidth() != newConstrainer->getMaximumWidth() - || newConstrainer->getMinimumHeight() != newConstrainer->getMaximumHeight()); + constrainer = newConstrainer; + updatePeer(); - attachConstrainer (newConstrainer); + if (constrainer != nullptr) + resizableByHost = (newConstrainer->getMinimumWidth() != newConstrainer->getMaximumWidth() + || newConstrainer->getMinimumHeight() != newConstrainer->getMaximumHeight()); if (resizableCorner != nullptr) attachResizableCornerComponent(); } } -void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) -{ - if (constrainer != newConstrainer) - { - constrainer = newConstrainer; - updatePeer(); - } -} - void AudioProcessorEditor::attachResizableCornerComponent() { - resizableCorner.reset (new ResizableCornerComponent (this, constrainer)); + resizableCorner = std::make_unique (this, constrainer); Component::addChildComponent (resizableCorner.get()); resizableCorner->setAlwaysOnTop (true); editorResized (true); @@ -194,11 +176,6 @@ void AudioProcessorEditor::editorResized (bool wasResized) getHeight() - resizerSize, resizerSize, resizerSize); } - - if (! resizable) - if (auto w = getWidth()) - if (auto h = getHeight()) - defaultConstrainer.setSizeLimits (w, h, w, h); } } diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index ff694b78fc..c925540c90 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -54,12 +54,12 @@ public: /** Destructor. */ ~AudioProcessorEditor() override; - //============================================================================== /** The AudioProcessor that this editor represents. */ AudioProcessor& processor; /** Returns a pointer to the processor that this editor represents. + This method is here to support legacy code, but it's easier to just use the AudioProcessorEditor::processor member variable directly to get this object. */ @@ -76,6 +76,7 @@ public: /** Some types of plugin can call this to suggest that the control for a particular parameter should be highlighted. + Currently only AAX plugins will call this, and implementing it is optional. */ virtual void setControlHighlight (ParameterControlHighlightInfo); @@ -117,36 +118,45 @@ public: virtual void setScaleFactor (float newScale); //============================================================================== - /** Marks the host's editor window as resizable - - @param allowHostToResize whether the editor's parent window can be resized - by the user or the host. Even if this is false, you - can still resize your window yourself by calling - setBounds (for example, when a user clicks on a button - in your editor to drop out a panel) which will bypass any - resizable/constraints checks. If you are using - your own corner resizer than this will also bypass - any checks. - @param useBottomRightCornerResizer + /** Sets whether the editor is resizable by the host and/or user. + + @param allowHostToResize whether the editor's parent window can be resized + by the host. Even if this is false, you can still + resize your window yourself by calling setBounds + (for example, when a user clicks on a button in + your editor to drop out a panel) which will bypass + any resizable/constraints checks. + @param useBottomRightCornerResizer if this is true, a ResizableCornerComponent will be + added to the editor's bottom-right to allow the user + to resize the editor regardless of the value of + `allowHostToResize`. + @see setResizeLimits, isResizable */ void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); - /** Returns true if the host is allowed to resize editor's parent window + /** Returns true if the host is allowed to resize the editor's parent window. @see setResizable */ - bool isResizable() const noexcept { return resizable; } + bool isResizable() const noexcept { return resizableByHost; } /** This sets the maximum and minimum sizes for the window. If the window's current size is outside these limits, it will be resized to make sure it's within them. + If you pass in a different minimum and maximum size, this will mark the editor + as resizable by the host. + A direct call to setBounds() will bypass any constraint checks, but when the window is dragged by the user or resized by other indirect means, the constrainer will limit the numbers involved. + Note that if you have set a custom constrainer for this editor then this will have + no effect, and if you have removed the constrainer with `setConstrainer (nullptr);` + then this will re-add the default constrainer with the new limits. + @see setResizable */ void setResizeLimits (int newMinimumWidth, @@ -154,8 +164,8 @@ public: int newMaximumWidth, int newMaximumHeight) noexcept; - /** Returns the bounds constrainer object that this window is using. + You can access this to change its properties. */ ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } @@ -176,11 +186,14 @@ public: */ void setBoundsConstrained (Rectangle newBounds); + /** The ResizableCornerComponent which is currently being used by this editor, + or nullptr if it does not have one. + */ std::unique_ptr resizableCorner; private: //============================================================================== - struct AudioProcessorEditorListener : ComponentListener + struct AudioProcessorEditorListener : public ComponentListener { AudioProcessorEditorListener (AudioProcessorEditor& e) : ed (e) {} @@ -198,14 +211,13 @@ private: void initialise(); void editorResized (bool wasResized); void updatePeer(); - void attachConstrainer (ComponentBoundsConstrainer*); void attachResizableCornerComponent(); //============================================================================== std::unique_ptr resizeListener; - bool resizable; + bool resizableByHost = false; ComponentBoundsConstrainer defaultConstrainer; - ComponentBoundsConstrainer* constrainer = {}; + ComponentBoundsConstrainer* constrainer = nullptr; Component::SafePointer splashScreen; AffineTransform hostScaleTransform; From fe9493867ede4eec22087a80717c47e8d28ae6b1 Mon Sep 17 00:00:00 2001 From: ed Date: Thu, 4 Mar 2021 09:22:40 +0000 Subject: [PATCH 16/17] Linux: Quote Bash command string in Process::openDocument() to prevent ampersands being treated as a control operator --- modules/juce_core/native/juce_linux_Files.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_core/native/juce_linux_Files.cpp b/modules/juce_core/native/juce_linux_Files.cpp index d2a302e3eb..5cd2d1843b 100644 --- a/modules/juce_core/native/juce_linux_Files.cpp +++ b/modules/juce_core/native/juce_linux_Files.cpp @@ -203,7 +203,7 @@ bool Process::openDocument (const String& fileName, const String& parameters) for (auto browserName : { "xdg-open", "/etc/alternatives/x-www-browser", "firefox", "mozilla", "google-chrome", "chromium-browser", "opera", "konqueror" }) { - cmdLines.add (String (browserName) + " " + cmdString.trim()); + cmdLines.add (String (browserName) + " " + cmdString.trim().quoted()); } cmdString = cmdLines.joinIntoString (" || "); From ba2027497b348a7fbe14f89cfde6666cb963983c Mon Sep 17 00:00:00 2001 From: ed Date: Thu, 4 Mar 2021 09:32:58 +0000 Subject: [PATCH 17/17] MIDI: Standardised and fixed some MIDI channel assertions --- modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp | 4 ++-- modules/juce_audio_basics/midi/juce_MidiRPN.cpp | 2 +- modules/juce_audio_basics/mpe/juce_MPEUtils.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp b/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp index 8d9ad2cd37..de83ae8514 100644 --- a/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp @@ -42,7 +42,7 @@ void MidiKeyboardState::reset() bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept { - jassert (midiChannel >= 0 && midiChannel <= 16); + jassert (midiChannel > 0 && midiChannel <= 16); return isPositiveAndBelow (n, 128) && (noteStates[n] & (1 << (midiChannel - 1))) != 0; @@ -56,7 +56,7 @@ bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const in void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) { - jassert (midiChannel >= 0 && midiChannel <= 16); + jassert (midiChannel > 0 && midiChannel <= 16); jassert (isPositiveAndBelow (midiNoteNumber, 128)); const ScopedLock sl (lock); diff --git a/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index f1c3d24f54..24a6c1f8d8 100644 --- a/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -36,7 +36,7 @@ bool MidiRPNDetector::parseControllerMessage (int midiChannel, int controllerValue, MidiRPNMessage& result) noexcept { - jassert (midiChannel >= 1 && midiChannel <= 16); + jassert (midiChannel > 0 && midiChannel <= 16); jassert (controllerNumber >= 0 && controllerNumber < 128); jassert (controllerValue >= 0 && controllerValue < 128); diff --git a/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp b/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp index 74b3c14408..a8a75a9222 100644 --- a/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp +++ b/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp @@ -97,7 +97,7 @@ void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) return false; }; - if (midiChannel >= 0 && midiChannel < 17) + if (midiChannel >= 0 && midiChannel <= 16) { removeNote (midiChannels[midiChannel], noteNumber); return;