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.

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