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.

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