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.

705 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. static int numAlwaysOnTopPeers = 0;
  22. bool juce_areThereAnyAlwaysOnTopWindows() { return numAlwaysOnTopPeers > 0; }
  23. //==============================================================================
  24. class LinuxComponentPeer : public ComponentPeer
  25. {
  26. public:
  27. LinuxComponentPeer (Component& comp, int windowStyleFlags, ::Window parentToAddTo)
  28. : ComponentPeer (comp, windowStyleFlags),
  29. isAlwaysOnTop (comp.isAlwaysOnTop())
  30. {
  31. // it's dangerous to create a window on a thread other than the message thread.
  32. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  33. if (isAlwaysOnTop)
  34. ++numAlwaysOnTopPeers;
  35. repainter = std::make_unique<LinuxRepaintManager> (*this);
  36. windowH = XWindowSystem::getInstance()->createWindow (parentToAddTo, this);
  37. parentWindow = parentToAddTo;
  38. setTitle (component.getName());
  39. getNativeRealtimeModifiers = []() -> ModifierKeys { return XWindowSystem::getInstance()->getNativeRealtimeModifiers(); };
  40. }
  41. ~LinuxComponentPeer() override
  42. {
  43. // it's dangerous to delete a window on a thread other than the message thread.
  44. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  45. repainter = nullptr;
  46. XWindowSystem::getInstance()->destroyWindow (windowH);
  47. if (isAlwaysOnTop)
  48. --numAlwaysOnTopPeers;
  49. }
  50. ::Window getWindowHandle() const noexcept
  51. {
  52. return windowH;
  53. }
  54. //==============================================================================
  55. void* getNativeHandle() const override
  56. {
  57. return reinterpret_cast<void*> (getWindowHandle());
  58. }
  59. //==============================================================================
  60. void setBounds (const Rectangle<int>& newBounds, bool isNowFullScreen) override
  61. {
  62. bounds = newBounds.withSize (jmax (1, newBounds.getWidth()),
  63. jmax (1, newBounds.getHeight()));
  64. updateScaleFactorFromNewBounds (bounds, false);
  65. auto physicalBounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds)
  66. : bounds * currentScaleFactor;
  67. WeakReference<Component> deletionChecker (&component);
  68. XWindowSystem::getInstance()->setBounds (windowH, physicalBounds, isNowFullScreen);
  69. fullScreen = isNowFullScreen;
  70. if (deletionChecker != nullptr)
  71. {
  72. updateBorderSize();
  73. handleMovedOrResized();
  74. }
  75. }
  76. Point<int> getScreenPosition (bool physical) const
  77. {
  78. auto physicalParentPosition = XWindowSystem::getInstance()->getPhysicalParentScreenPosition();
  79. auto parentPosition = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalParentPosition)
  80. : physicalParentPosition / currentScaleFactor;
  81. auto screenBounds = parentWindow == 0 ? bounds
  82. : bounds.translated (parentPosition.x, parentPosition.y);
  83. if (physical)
  84. return parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft())
  85. : screenBounds.getTopLeft() * currentScaleFactor;
  86. return screenBounds.getTopLeft();
  87. }
  88. Rectangle<int> getBounds() const override
  89. {
  90. return bounds;
  91. }
  92. BorderSize<int> getFrameSize() const override
  93. {
  94. return windowBorder;
  95. }
  96. Point<float> localToGlobal (Point<float> relativePosition) override
  97. {
  98. return relativePosition + getScreenPosition (false).toFloat();
  99. }
  100. Point<float> globalToLocal (Point<float> screenPosition) override
  101. {
  102. return screenPosition - getScreenPosition (false).toFloat();
  103. }
  104. using ComponentPeer::localToGlobal;
  105. using ComponentPeer::globalToLocal;
  106. //==============================================================================
  107. StringArray getAvailableRenderingEngines() override
  108. {
  109. return { "Software Renderer" };
  110. }
  111. void setVisible (bool shouldBeVisible) override
  112. {
  113. XWindowSystem::getInstance()->setVisible (windowH, shouldBeVisible);
  114. }
  115. void setTitle (const String& title) override
  116. {
  117. XWindowSystem::getInstance()->setTitle (windowH, title);
  118. }
  119. void setMinimised (bool shouldBeMinimised) override
  120. {
  121. if (shouldBeMinimised)
  122. XWindowSystem::getInstance()->setMinimised (windowH, shouldBeMinimised);
  123. else
  124. setVisible (true);
  125. }
  126. bool isMinimised() const override
  127. {
  128. return XWindowSystem::getInstance()->isMinimised (windowH);
  129. }
  130. void setFullScreen (bool shouldBeFullScreen) override
  131. {
  132. auto r = lastNonFullscreenBounds; // (get a copy of this before de-minimising)
  133. setMinimised (false);
  134. if (fullScreen != shouldBeFullScreen)
  135. {
  136. XWindowSystem::getInstance()->setMaximised (windowH, shouldBeFullScreen);
  137. if (shouldBeFullScreen)
  138. r = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow);
  139. if (! r.isEmpty())
  140. setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);
  141. component.repaint();
  142. }
  143. }
  144. bool isFullScreen() const override
  145. {
  146. return fullScreen;
  147. }
  148. bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
  149. {
  150. if (! bounds.withZeroOrigin().contains (localPos))
  151. return false;
  152. for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
  153. {
  154. auto* c = Desktop::getInstance().getComponent (i);
  155. if (c == &component)
  156. break;
  157. if (! c->isVisible())
  158. continue;
  159. if (auto* peer = c->getPeer())
  160. if (peer->contains (localPos + bounds.getPosition() - peer->getBounds().getPosition(), true))
  161. return false;
  162. }
  163. if (trueIfInAChildWindow)
  164. return true;
  165. return XWindowSystem::getInstance()->contains (windowH, localPos * currentScaleFactor);
  166. }
  167. void toFront (bool makeActive) override
  168. {
  169. if (makeActive)
  170. {
  171. setVisible (true);
  172. grabFocus();
  173. }
  174. XWindowSystem::getInstance()->toFront (windowH, makeActive);
  175. handleBroughtToFront();
  176. }
  177. void toBehind (ComponentPeer* other) override
  178. {
  179. if (auto* otherPeer = dynamic_cast<LinuxComponentPeer*> (other))
  180. {
  181. if (otherPeer->styleFlags & windowIsTemporary)
  182. return;
  183. setMinimised (false);
  184. XWindowSystem::getInstance()->toBehind (windowH, otherPeer->windowH);
  185. }
  186. else
  187. {
  188. jassertfalse; // wrong type of window?
  189. }
  190. }
  191. bool isFocused() const override
  192. {
  193. return XWindowSystem::getInstance()->isFocused (windowH);
  194. }
  195. void grabFocus() override
  196. {
  197. if (XWindowSystem::getInstance()->grabFocus (windowH))
  198. isActiveApplication = true;
  199. }
  200. //==============================================================================
  201. void repaint (const Rectangle<int>& area) override
  202. {
  203. repainter->repaint (area.getIntersection (bounds.withZeroOrigin()));
  204. }
  205. void performAnyPendingRepaintsNow() override
  206. {
  207. repainter->performAnyPendingRepaintsNow();
  208. }
  209. void setIcon (const Image& newIcon) override
  210. {
  211. XWindowSystem::getInstance()->setIcon (windowH, newIcon);
  212. }
  213. double getPlatformScaleFactor() const noexcept override
  214. {
  215. return currentScaleFactor;
  216. }
  217. void setAlpha (float) override {}
  218. bool setAlwaysOnTop (bool) override { return false; }
  219. void textInputRequired (Point<int>, TextInputTarget&) override {}
  220. //==============================================================================
  221. void addOpenGLRepaintListener (Component* dummy)
  222. {
  223. if (dummy != nullptr)
  224. glRepaintListeners.addIfNotAlreadyThere (dummy);
  225. }
  226. void removeOpenGLRepaintListener (Component* dummy)
  227. {
  228. if (dummy != nullptr)
  229. glRepaintListeners.removeAllInstancesOf (dummy);
  230. }
  231. void repaintOpenGLContexts()
  232. {
  233. for (auto* c : glRepaintListeners)
  234. c->handleCommandMessage (0);
  235. }
  236. //==============================================================================
  237. ::Window getParentWindow() { return parentWindow; }
  238. void setParentWindow (::Window newParent) { parentWindow = newParent; }
  239. //==============================================================================
  240. void updateWindowBounds()
  241. {
  242. jassert (windowH != 0);
  243. if (windowH != 0)
  244. {
  245. auto physicalBounds = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow);
  246. updateScaleFactorFromNewBounds (physicalBounds, true);
  247. bounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds)
  248. : physicalBounds / currentScaleFactor;
  249. }
  250. }
  251. void updateBorderSize()
  252. {
  253. if ((styleFlags & windowHasTitleBar) == 0)
  254. windowBorder = {};
  255. else if (windowBorder.getTopAndBottom() == 0 && windowBorder.getLeftAndRight() == 0)
  256. windowBorder = XWindowSystem::getInstance()->getBorderSize (windowH);
  257. }
  258. //==============================================================================
  259. static bool isActiveApplication;
  260. bool focused = false;
  261. private:
  262. //==============================================================================
  263. class LinuxRepaintManager : public Timer
  264. {
  265. public:
  266. LinuxRepaintManager (LinuxComponentPeer& p)
  267. : peer (p),
  268. isSemiTransparentWindow ((peer.getStyleFlags() & ComponentPeer::windowIsSemiTransparent) != 0)
  269. {
  270. }
  271. void timerCallback() override
  272. {
  273. XWindowSystem::getInstance()->processPendingPaintsForWindow (peer.windowH);
  274. if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
  275. return;
  276. if (! regionsNeedingRepaint.isEmpty())
  277. {
  278. stopTimer();
  279. performAnyPendingRepaintsNow();
  280. }
  281. else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + 3000)
  282. {
  283. stopTimer();
  284. image = Image();
  285. }
  286. }
  287. void repaint (Rectangle<int> area)
  288. {
  289. if (! isTimerRunning())
  290. startTimer (repaintTimerPeriod);
  291. regionsNeedingRepaint.add (area * peer.currentScaleFactor);
  292. }
  293. void performAnyPendingRepaintsNow()
  294. {
  295. if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
  296. {
  297. startTimer (repaintTimerPeriod);
  298. return;
  299. }
  300. auto originalRepaintRegion = regionsNeedingRepaint;
  301. regionsNeedingRepaint.clear();
  302. auto totalArea = originalRepaintRegion.getBounds();
  303. if (! totalArea.isEmpty())
  304. {
  305. if (image.isNull() || image.getWidth() < totalArea.getWidth()
  306. || image.getHeight() < totalArea.getHeight())
  307. {
  308. image = XWindowSystem::getInstance()->createImage (isSemiTransparentWindow,
  309. totalArea.getWidth(), totalArea.getHeight(),
  310. useARGBImagesForRendering);
  311. }
  312. startTimer (repaintTimerPeriod);
  313. RectangleList<int> adjustedList (originalRepaintRegion);
  314. adjustedList.offsetAll (-totalArea.getX(), -totalArea.getY());
  315. if (XWindowSystem::getInstance()->canUseARGBImages())
  316. for (auto& i : originalRepaintRegion)
  317. image.clear (i - totalArea.getPosition());
  318. {
  319. auto context = peer.getComponent().getLookAndFeel()
  320. .createGraphicsContext (image, -totalArea.getPosition(), adjustedList);
  321. context->addTransform (AffineTransform::scale ((float) peer.currentScaleFactor));
  322. peer.handlePaint (*context);
  323. }
  324. for (auto& i : originalRepaintRegion)
  325. XWindowSystem::getInstance()->blitToWindow (peer.windowH, image, i, totalArea);
  326. }
  327. lastTimeImageUsed = Time::getApproximateMillisecondCounter();
  328. startTimer (repaintTimerPeriod);
  329. }
  330. private:
  331. enum { repaintTimerPeriod = 1000 / 100 };
  332. LinuxComponentPeer& peer;
  333. const bool isSemiTransparentWindow;
  334. Image image;
  335. uint32 lastTimeImageUsed = 0;
  336. RectangleList<int> regionsNeedingRepaint;
  337. bool useARGBImagesForRendering = XWindowSystem::getInstance()->canUseARGBImages();
  338. JUCE_DECLARE_NON_COPYABLE (LinuxRepaintManager)
  339. };
  340. //==============================================================================
  341. void updateScaleFactorFromNewBounds (const Rectangle<int>& newBounds, bool isPhysical)
  342. {
  343. Point<int> translation = (parentWindow != 0 ? getScreenPosition (isPhysical) : Point<int>());
  344. const auto& desktop = Desktop::getInstance();
  345. if (auto* display = desktop.getDisplays().getDisplayForRect (newBounds.translated (translation.x, translation.y),
  346. isPhysical))
  347. {
  348. auto newScaleFactor = display->scale / desktop.getGlobalScaleFactor();
  349. if (! approximatelyEqual (newScaleFactor, currentScaleFactor))
  350. {
  351. currentScaleFactor = newScaleFactor;
  352. scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (currentScaleFactor); });
  353. }
  354. }
  355. }
  356. //==============================================================================
  357. std::unique_ptr<LinuxRepaintManager> repainter;
  358. ::Window windowH = {}, parentWindow = {};
  359. Rectangle<int> bounds;
  360. BorderSize<int> windowBorder;
  361. bool fullScreen = false, isAlwaysOnTop = false;
  362. double currentScaleFactor = 1.0;
  363. Array<Component*> glRepaintListeners;
  364. //==============================================================================
  365. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LinuxComponentPeer)
  366. };
  367. bool LinuxComponentPeer::isActiveApplication = false;
  368. //==============================================================================
  369. ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindowToAttachTo)
  370. {
  371. return new LinuxComponentPeer (*this, styleFlags, (::Window) nativeWindowToAttachTo);
  372. }
  373. //==============================================================================
  374. JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return LinuxComponentPeer::isActiveApplication; }
  375. JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {}
  376. JUCE_API void JUCE_CALLTYPE Process::hide() {}
  377. //==============================================================================
  378. void Desktop::setKioskComponent (Component* comp, bool enableOrDisable, bool)
  379. {
  380. if (enableOrDisable)
  381. comp->setBounds (getDisplays().getDisplayForRect (comp->getScreenBounds())->totalArea);
  382. }
  383. void Displays::findDisplays (float masterScale)
  384. {
  385. if (XWindowSystem::getInstance()->getDisplay() != nullptr)
  386. {
  387. displays = XWindowSystem::getInstance()->findDisplays (masterScale);
  388. if (! displays.isEmpty())
  389. updateToLogical();
  390. }
  391. }
  392. bool Desktop::canUseSemiTransparentWindows() noexcept
  393. {
  394. return XWindowSystem::getInstance()->canUseSemiTransparentWindows();
  395. }
  396. static bool screenSaverAllowed = true;
  397. void Desktop::setScreenSaverEnabled (bool isEnabled)
  398. {
  399. if (screenSaverAllowed != isEnabled)
  400. {
  401. screenSaverAllowed = isEnabled;
  402. XWindowSystem::getInstance()->setScreenSaverEnabled (screenSaverAllowed);
  403. }
  404. }
  405. bool Desktop::isScreenSaverEnabled()
  406. {
  407. return screenSaverAllowed;
  408. }
  409. double Desktop::getDefaultMasterScale() { return 1.0; }
  410. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { return upright; }
  411. void Desktop::allowedOrientationsChanged() {}
  412. //==============================================================================
  413. bool MouseInputSource::SourceList::addSource()
  414. {
  415. if (sources.isEmpty())
  416. {
  417. addSource (0, MouseInputSource::InputSourceType::mouse);
  418. return true;
  419. }
  420. return false;
  421. }
  422. bool MouseInputSource::SourceList::canUseTouch()
  423. {
  424. return false;
  425. }
  426. Point<float> MouseInputSource::getCurrentRawMousePosition()
  427. {
  428. return Desktop::getInstance().getDisplays().physicalToLogical (XWindowSystem::getInstance()->getCurrentMousePosition());
  429. }
  430. void MouseInputSource::setRawMousePosition (Point<float> newPosition)
  431. {
  432. XWindowSystem::getInstance()->setMousePosition (Desktop::getInstance().getDisplays().logicalToPhysical (newPosition));
  433. }
  434. //==============================================================================
  435. void* CustomMouseCursorInfo::create() const
  436. {
  437. return XWindowSystem::getInstance()->createCustomMouseCursorInfo (image, hotspot);
  438. }
  439. void MouseCursor::deleteMouseCursor (void* cursorHandle, bool)
  440. {
  441. if (cursorHandle != nullptr)
  442. XWindowSystem::getInstance()->deleteMouseCursor (cursorHandle);
  443. }
  444. void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type)
  445. {
  446. return XWindowSystem::getInstance()->createStandardMouseCursor (type);
  447. }
  448. void MouseCursor::showInWindow (ComponentPeer* peer) const
  449. {
  450. if (peer != nullptr)
  451. XWindowSystem::getInstance()->showCursor ((::Window) peer->getNativeHandle(), getHandle());
  452. }
  453. //==============================================================================
  454. static LinuxComponentPeer* getPeerForDragEvent (Component* sourceComp)
  455. {
  456. if (sourceComp == nullptr)
  457. if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0))
  458. sourceComp = draggingSource->getComponentUnderMouse();
  459. if (sourceComp != nullptr)
  460. if (auto* lp = dynamic_cast<LinuxComponentPeer*> (sourceComp->getPeer()))
  461. return lp;
  462. jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
  463. return nullptr;
  464. }
  465. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
  466. Component* sourceComp, std::function<void()> callback)
  467. {
  468. if (files.isEmpty())
  469. return false;
  470. if (auto* peer = getPeerForDragEvent (sourceComp))
  471. return XWindowSystem::getInstance()->externalDragFileInit (peer, files, canMoveFiles, std::move (callback));
  472. // This method must be called in response to a component's mouseDown or mouseDrag event!
  473. jassertfalse;
  474. return false;
  475. }
  476. bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComp,
  477. std::function<void()> callback)
  478. {
  479. if (text.isEmpty())
  480. return false;
  481. if (auto* peer = getPeerForDragEvent (sourceComp))
  482. return XWindowSystem::getInstance()->externalDragTextInit (peer, text, std::move (callback));
  483. // This method must be called in response to a component's mouseDown or mouseDrag event!
  484. jassertfalse;
  485. return false;
  486. }
  487. //==============================================================================
  488. void SystemClipboard::copyTextToClipboard (const String& clipText)
  489. {
  490. XWindowSystem::getInstance()->copyTextToClipboard (clipText);
  491. }
  492. String SystemClipboard::getTextFromClipboard()
  493. {
  494. return XWindowSystem::getInstance()->getTextFromClipboard();
  495. }
  496. //==============================================================================
  497. bool KeyPress::isKeyCurrentlyDown (int keyCode)
  498. {
  499. return XWindowSystem::getInstance()->isKeyCurrentlyDown (keyCode);
  500. }
  501. void LookAndFeel::playAlertSound()
  502. {
  503. std::cout << "\a" << std::flush;
  504. }
  505. //==============================================================================
  506. #if JUCE_MODAL_LOOPS_PERMITTED
  507. void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType iconType,
  508. const String& title, const String& message,
  509. Component*)
  510. {
  511. AlertWindow::showMessageBox (iconType, title, message);
  512. }
  513. #endif
  514. void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType iconType,
  515. const String& title, const String& message,
  516. Component* associatedComponent,
  517. ModalComponentManager::Callback* callback)
  518. {
  519. AlertWindow::showMessageBoxAsync (iconType, title, message, {}, associatedComponent, callback);
  520. }
  521. bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType iconType,
  522. const String& title, const String& message,
  523. Component* associatedComponent,
  524. ModalComponentManager::Callback* callback)
  525. {
  526. return AlertWindow::showOkCancelBox (iconType, title, message, {}, {}, associatedComponent, callback);
  527. }
  528. int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType iconType,
  529. const String& title, const String& message,
  530. Component* associatedComponent,
  531. ModalComponentManager::Callback* callback)
  532. {
  533. return AlertWindow::showYesNoCancelBox (iconType, title, message, {}, {}, {},
  534. associatedComponent, callback);
  535. }
  536. int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType iconType,
  537. const String& title, const String& message,
  538. Component* associatedComponent,
  539. ModalComponentManager::Callback* callback)
  540. {
  541. return AlertWindow::showOkCancelBox (iconType, title, message, TRANS ("Yes"), TRANS ("No"),
  542. associatedComponent, callback);
  543. }
  544. //==============================================================================
  545. Image juce_createIconForFile (const File&)
  546. {
  547. return {};
  548. }
  549. void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy)
  550. {
  551. if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
  552. linuxPeer->addOpenGLRepaintListener (dummy);
  553. }
  554. void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy)
  555. {
  556. if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
  557. linuxPeer->removeOpenGLRepaintListener (dummy);
  558. }
  559. } // namespace juce