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.

224 lines
7.2KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. /**
  20. This component runs in a slave process, draws the part of the canvas that this
  21. particular client covers, and updates itself when messages arrive from the master
  22. containing new canvas states.
  23. */
  24. class SlaveCanvasComponent : public Component,
  25. private OSCSender,
  26. private OSCReceiver,
  27. private OSCReceiver::Listener<OSCReceiver::RealtimeCallback>,
  28. private AsyncUpdater,
  29. private Timer
  30. {
  31. public:
  32. SlaveCanvasComponent (PropertiesFile& p, int windowIndex) : properties (p)
  33. {
  34. {
  35. String uuidPropName ("UUID" + String (windowIndex));
  36. clientName = properties.getValue (uuidPropName);
  37. if (clientName.isEmpty())
  38. {
  39. clientName = "CLIENT_" + String (Random().nextInt (10000)).toUpperCase();
  40. properties.setValue (uuidPropName, clientName);
  41. }
  42. }
  43. setOpaque (true);
  44. setSize (1500, 900);
  45. if (! OSCSender::connect (getBroadcastIPAddress(), clientPortNumber))
  46. error = "Client app OSC sender: network connection error.";
  47. if (! OSCReceiver::connect (masterPortNumber))
  48. error = "Client app OSC receiver: network connection error.";
  49. OSCReceiver::addListener (this);
  50. timerCallback();
  51. startTimer (2000);
  52. }
  53. ~SlaveCanvasComponent()
  54. {
  55. OSCReceiver::removeListener (this);
  56. }
  57. private:
  58. void mouseDrag (const MouseEvent& e) override
  59. {
  60. auto clientArea = getAreaInGlobalSpace();
  61. if (! clientArea.isEmpty())
  62. {
  63. OSCMessage message (userInputOSCAddress);
  64. message.addString (clientName);
  65. message.addFloat32 (e.position.x * clientArea.getWidth() / getWidth() + clientArea.getX());
  66. message.addFloat32 (e.position.y * clientArea.getHeight() / getHeight() + clientArea.getY());
  67. send (message);
  68. }
  69. }
  70. //==============================================================================
  71. void oscMessageReceived (const OSCMessage& message) override
  72. {
  73. auto address = message.getAddressPattern();
  74. if (address.matches (canvasStateOSCAddress))
  75. canvasStateOSCMessageReceived (message);
  76. }
  77. struct NewStateMessage : public Message
  78. {
  79. NewStateMessage (const MemoryBlock& d) : data (d) {}
  80. MemoryBlock data;
  81. };
  82. void canvasStateOSCMessageReceived (const OSCMessage& message)
  83. {
  84. if (message.isEmpty() || ! message[0].isBlob())
  85. return;
  86. if (packetiser.appendIncomingBlock (message[0].getBlob()))
  87. {
  88. const ScopedLock sl (canvasLock);
  89. MemoryBlock newCanvasData;
  90. if (packetiser.reassemble (newCanvasData))
  91. {
  92. MemoryInputStream i (newCanvasData.getData(), newCanvasData.getSize(), false);
  93. canvas2.load (i);
  94. triggerAsyncUpdate();
  95. }
  96. }
  97. }
  98. //==============================================================================
  99. String getMachineInfoToDisplay() const
  100. {
  101. //auto display = Desktop::getInstance().getDisplays().getDisplayContaining (getScreenBounds().getCentre());
  102. return getOSName();// + " " + String (display.dpi) + " " + String (display.scale);
  103. }
  104. static String getOSName()
  105. {
  106. #if JUCE_MAC
  107. return "Mac OSX";
  108. #elif JUCE_ANDROID
  109. return "Android";
  110. #elif JUCE_IOS
  111. return "iOS";
  112. #elif JUCE_WINDOWS
  113. return "Windows";
  114. #elif JUCE_LINUX
  115. return "Linux";
  116. #endif
  117. }
  118. void paint (Graphics& g) override
  119. {
  120. g.fillAll (canvas.backgroundColour);
  121. auto clientArea = getAreaInGlobalSpace();
  122. if (clientArea.isEmpty())
  123. {
  124. g.setColour (Colours::red.withAlpha (0.5f));
  125. g.setFont (20.0f);
  126. g.drawText ("Not Connected", getLocalBounds(), Justification::centred, false);
  127. return;
  128. }
  129. canvas.draw (g, getLocalBounds().toFloat(), clientArea);
  130. g.setFont (Font (34.0f));
  131. g.setColour (Colours::white.withAlpha (0.6f));
  132. g.drawText (getMachineInfoToDisplay(),
  133. getLocalBounds().reduced (10).removeFromBottom (20),
  134. Justification::centredRight, true);
  135. if (error.isNotEmpty())
  136. {
  137. g.setColour (Colours::red);
  138. g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
  139. Justification::centredRight, true);
  140. }
  141. }
  142. Rectangle<float> getAreaInGlobalSpace() const
  143. {
  144. if (auto client = canvas.findClient (clientName))
  145. {
  146. auto screenBounds = getScreenBounds();
  147. auto display = Desktop::getInstance().getDisplays().getDisplayContaining (screenBounds.getCentre());
  148. return ((screenBounds - display.userArea.getCentre()).toFloat() / (client->scaleFactor * display.dpi / display.scale)) + client->centre;
  149. }
  150. return {};
  151. }
  152. Rectangle<float> getScreenAreaInGlobalSpace() const
  153. {
  154. if (auto client = canvas.findClient (clientName))
  155. {
  156. auto display = Desktop::getInstance().getDisplays().getDisplayContaining (getScreenBounds().getCentre());
  157. return (display.userArea.toFloat() / (client->scaleFactor * display.dpi / display.scale)).withCentre (client->centre);
  158. }
  159. return {};
  160. }
  161. void timerCallback() override
  162. {
  163. send (newClientOSCAddress, clientName + ":" + getIPAddress()
  164. + ":" + getScreenAreaInGlobalSpace().toString());
  165. }
  166. void handleAsyncUpdate() override
  167. {
  168. const ScopedLock sl (canvasLock);
  169. canvas.swapWith (canvas2);
  170. repaint();
  171. }
  172. SharedCanvasDescription canvas, canvas2;
  173. PropertiesFile& properties;
  174. String clientName, error;
  175. CriticalSection canvasLock;
  176. BlockPacketiser packetiser;
  177. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlaveCanvasComponent)
  178. };