The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
10KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. //==============================================================================
  21. /*
  22. Forwards NSNotificationCenter callbacks to a std::function<void()>.
  23. */
  24. class FunctionNotificationCenterObserver
  25. {
  26. public:
  27. FunctionNotificationCenterObserver (NSNotificationName notificationName,
  28. id objectToObserve,
  29. std::function<void()> callback)
  30. : onNotification (std::move (callback)),
  31. observer (observerObject.get(), getSelector(), notificationName, objectToObserve)
  32. {}
  33. private:
  34. struct ObserverClass
  35. {
  36. ObserverClass()
  37. {
  38. klass.addIvar<FunctionNotificationCenterObserver*> ("owner");
  39. klass.addMethod (getSelector(), [] (id self, SEL, NSNotification*)
  40. {
  41. getIvar<FunctionNotificationCenterObserver*> (self, "owner")->onNotification();
  42. });
  43. klass.registerClass();
  44. }
  45. NSObject* createInstance() const { return klass.createInstance(); }
  46. private:
  47. ObjCClass<NSObject> klass { "JUCEObserverClass_" };
  48. };
  49. static SEL getSelector()
  50. {
  51. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  52. return @selector (notificationFired:);
  53. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  54. }
  55. std::function<void()> onNotification;
  56. NSUniquePtr<NSObject> observerObject
  57. {
  58. [this]
  59. {
  60. static ObserverClass observerClass;
  61. auto* result = observerClass.createInstance();
  62. object_setInstanceVariable (result, "owner", this);
  63. return result;
  64. }()
  65. };
  66. ScopedNotificationCenterObserver observer;
  67. // Instances can't be copied or moved, because 'this' is stored as a member of the ObserverClass
  68. // object.
  69. JUCE_DECLARE_NON_COPYABLE (FunctionNotificationCenterObserver)
  70. JUCE_DECLARE_NON_MOVEABLE (FunctionNotificationCenterObserver)
  71. };
  72. //==============================================================================
  73. /*
  74. Manages the lifetime of a CVDisplayLinkRef for a single display, and automatically starts and
  75. stops it.
  76. */
  77. class ScopedDisplayLink
  78. {
  79. public:
  80. static CGDirectDisplayID getDisplayIdForScreen (NSScreen* screen)
  81. {
  82. return (CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue];
  83. }
  84. ScopedDisplayLink (NSScreen* screenIn, std::function<void()> onCallbackIn)
  85. : displayId (getDisplayIdForScreen (screenIn)),
  86. link ([display = displayId]
  87. {
  88. CVDisplayLinkRef ptr = nullptr;
  89. [[maybe_unused]] const auto result = CVDisplayLinkCreateWithCGDisplay (display, &ptr);
  90. jassert (result == kCVReturnSuccess);
  91. jassert (ptr != nullptr);
  92. return ptr;
  93. }()),
  94. onCallback (std::move (onCallbackIn))
  95. {
  96. const auto callback = [] (CVDisplayLinkRef,
  97. const CVTimeStamp*,
  98. const CVTimeStamp*,
  99. CVOptionFlags,
  100. CVOptionFlags*,
  101. void* context) -> int
  102. {
  103. static_cast<const ScopedDisplayLink*> (context)->onCallback();
  104. return kCVReturnSuccess;
  105. };
  106. [[maybe_unused]] const auto callbackResult = CVDisplayLinkSetOutputCallback (link.get(), callback, this);
  107. jassert (callbackResult == kCVReturnSuccess);
  108. [[maybe_unused]] const auto startResult = CVDisplayLinkStart (link.get());
  109. jassert (startResult == kCVReturnSuccess);
  110. }
  111. ~ScopedDisplayLink() noexcept
  112. {
  113. if (link != nullptr)
  114. CVDisplayLinkStop (link.get());
  115. }
  116. CGDirectDisplayID getDisplayId() const { return displayId; }
  117. double getNominalVideoRefreshPeriodS() const
  118. {
  119. const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (link.get());
  120. if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
  121. return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
  122. return 0.0;
  123. }
  124. private:
  125. struct DisplayLinkDestructor
  126. {
  127. void operator() (CVDisplayLinkRef ptr) const
  128. {
  129. if (ptr != nullptr)
  130. CVDisplayLinkRelease (ptr);
  131. }
  132. };
  133. CGDirectDisplayID displayId;
  134. std::unique_ptr<std::remove_pointer_t<CVDisplayLinkRef>, DisplayLinkDestructor> link;
  135. std::function<void()> onCallback;
  136. // Instances can't be copied or moved, because 'this' is passed as context to
  137. // CVDisplayLinkSetOutputCallback
  138. JUCE_DECLARE_NON_COPYABLE (ScopedDisplayLink)
  139. JUCE_DECLARE_NON_MOVEABLE (ScopedDisplayLink)
  140. };
  141. //==============================================================================
  142. /*
  143. Holds a ScopedDisplayLink for each screen. When the screen configuration changes, the
  144. ScopedDisplayLinks will be recreated automatically to match the new configuration.
  145. */
  146. class PerScreenDisplayLinks
  147. {
  148. public:
  149. PerScreenDisplayLinks()
  150. {
  151. refreshScreens();
  152. }
  153. using RefreshCallback = std::function<void()>;
  154. using Factory = std::function<RefreshCallback (CGDirectDisplayID)>;
  155. /*
  156. Automatically unregisters a CVDisplayLink callback factory when ~Connection() is called.
  157. */
  158. class Connection
  159. {
  160. public:
  161. Connection() = default;
  162. Connection (PerScreenDisplayLinks& linksIn, std::list<Factory>::const_iterator it)
  163. : links (&linksIn), iter (it) {}
  164. ~Connection() noexcept
  165. {
  166. if (links != nullptr)
  167. links->unregisterFactory (iter);
  168. }
  169. Connection (const Connection&) = delete;
  170. Connection& operator= (const Connection&) = delete;
  171. Connection (Connection&& other) noexcept
  172. : links (std::exchange (other.links, nullptr)), iter (other.iter) {}
  173. Connection& operator= (Connection&& other) noexcept
  174. {
  175. Connection { std::move (other) }.swap (*this);
  176. return *this;
  177. }
  178. private:
  179. void swap (Connection& other) noexcept
  180. {
  181. std::swap (other.links, links);
  182. std::swap (other.iter, iter);
  183. }
  184. PerScreenDisplayLinks* links = nullptr;
  185. std::list<Factory>::const_iterator iter;
  186. };
  187. /* Stores the provided factory for as long as the returned Connection remains alive.
  188. Whenever the screen configuration changes, the factory function will be called for each
  189. screen. The RefreshCallback returned by the factory will be called every time that screen's
  190. display link callback fires.
  191. */
  192. [[nodiscard]] Connection registerFactory (Factory factory)
  193. {
  194. const ScopedLock lock (mutex);
  195. factories.push_front (std::move (factory));
  196. refreshScreens();
  197. return { *this, factories.begin() };
  198. }
  199. double getNominalVideoRefreshPeriodSForScreen (CGDirectDisplayID display) const
  200. {
  201. const ScopedLock lock (mutex);
  202. for (const auto& link : links)
  203. if (link.getDisplayId() == display)
  204. return link.getNominalVideoRefreshPeriodS();
  205. return 0.0;
  206. }
  207. private:
  208. void unregisterFactory (std::list<Factory>::const_iterator iter)
  209. {
  210. const ScopedLock lock (mutex);
  211. factories.erase (iter);
  212. refreshScreens();
  213. }
  214. void refreshScreens()
  215. {
  216. auto newLinks = [&]
  217. {
  218. std::list<ScopedDisplayLink> result;
  219. for (NSScreen* screen in [NSScreen screens])
  220. {
  221. std::vector<RefreshCallback> callbacks;
  222. for (auto& factory : factories)
  223. callbacks.push_back (factory (ScopedDisplayLink::getDisplayIdForScreen (screen)));
  224. // This is the callback that will actually fire in response to this screen's display
  225. // link callback.
  226. result.emplace_back (screen, [cbs = std::move (callbacks)]
  227. {
  228. for (const auto& callback : cbs)
  229. callback();
  230. });
  231. }
  232. return result;
  233. }();
  234. const ScopedLock lock (mutex);
  235. links = std::move (newLinks);
  236. }
  237. CriticalSection mutex;
  238. // This is a list rather than a vector so that the iterators are stable, even when items are
  239. // added/removed from the list. This is important because Connection objects store an iterator
  240. // internally, and may be created/destroyed arbitrarily.
  241. std::list<Factory> factories;
  242. // This is a list rather than a vector because ScopedDisplayLink is non-moveable.
  243. std::list<ScopedDisplayLink> links;
  244. FunctionNotificationCenterObserver screenParamsObserver { NSApplicationDidChangeScreenParametersNotification,
  245. nullptr,
  246. [this] { refreshScreens(); } };
  247. };
  248. } // namespace juce