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.

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