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.

283 lines
9.6KB

  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. #include "../JuceDemoHeader.h"
  20. #if JUCE_WINDOWS || JUCE_MAC || JUCE_LINUX
  21. //==============================================================================
  22. // This is a token that's used at both ends of our parent-child processes, to
  23. // act as a unique token in the command line arguments.
  24. static const char* demoCommandLineUID = "demoUID";
  25. // A few quick utility functions to convert between raw data and ValueTrees
  26. static ValueTree memoryBlockToValueTree (const MemoryBlock& mb)
  27. {
  28. return ValueTree::readFromData (mb.getData(), mb.getSize());
  29. }
  30. static MemoryBlock valueTreeToMemoryBlock (const ValueTree& v)
  31. {
  32. MemoryOutputStream mo;
  33. v.writeToStream (mo);
  34. return mo.getMemoryBlock();
  35. }
  36. static String valueTreeToString (const ValueTree& v)
  37. {
  38. const ScopedPointer<XmlElement> xml (v.createXml());
  39. return xml != nullptr ? xml->createDocument ("", true, false) : String();
  40. }
  41. //==============================================================================
  42. class ChildProcessDemo : public Component,
  43. private Button::Listener,
  44. private MessageListener
  45. {
  46. public:
  47. ChildProcessDemo()
  48. {
  49. setOpaque (true);
  50. addAndMakeVisible (launchButton);
  51. launchButton.setButtonText ("Launch Child Process");
  52. launchButton.addListener (this);
  53. addAndMakeVisible (pingButton);
  54. pingButton.setButtonText ("Send Ping");
  55. pingButton.addListener (this);
  56. addAndMakeVisible (killButton);
  57. killButton.setButtonText ("Kill Child Process");
  58. killButton.addListener (this);
  59. addAndMakeVisible (testResultsBox);
  60. testResultsBox.setMultiLine (true);
  61. testResultsBox.setFont (Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::plain));
  62. logMessage (String ("This demo uses the ChildProcessMaster and ChildProcessSlave classes to launch and communicate "
  63. "with a child process, sending messages in the form of serialised ValueTree objects.") + newLine);
  64. }
  65. ~ChildProcessDemo()
  66. {
  67. masterProcess = nullptr;
  68. }
  69. void paint (Graphics& g) override
  70. {
  71. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  72. }
  73. void resized() override
  74. {
  75. Rectangle<int> area (getLocalBounds());
  76. Rectangle<int> top (area.removeFromTop (40));
  77. launchButton.setBounds (top.removeFromLeft (180).reduced (8));
  78. pingButton.setBounds (top.removeFromLeft (180).reduced (8));
  79. killButton.setBounds (top.removeFromLeft (180).reduced (8));
  80. testResultsBox.setBounds (area.reduced (8));
  81. }
  82. // Appends a message to the textbox that's shown in the demo as the console
  83. void logMessage (const String& message)
  84. {
  85. postMessage (new LogMessage (message));
  86. }
  87. // invoked by the 'launch' button.
  88. void launchChildProcess()
  89. {
  90. if (masterProcess == nullptr)
  91. {
  92. masterProcess = new DemoMasterProcess (*this);
  93. if (masterProcess->launchSlaveProcess (File::getSpecialLocation (File::currentExecutableFile), demoCommandLineUID))
  94. logMessage ("Child process started");
  95. }
  96. }
  97. // invoked by the 'ping' button.
  98. void pingChildProcess()
  99. {
  100. if (masterProcess != nullptr)
  101. masterProcess->sendPingMessageToSlave();
  102. else
  103. logMessage ("Child process is not running!");
  104. }
  105. // invoked by the 'kill' button.
  106. void killChildProcess()
  107. {
  108. if (masterProcess != nullptr)
  109. {
  110. masterProcess = nullptr;
  111. logMessage ("Child process killed");
  112. }
  113. }
  114. //==============================================================================
  115. // This class is used by the main process, acting as the master and receiving messages
  116. // from the slave process.
  117. class DemoMasterProcess : public ChildProcessMaster,
  118. private DeletedAtShutdown
  119. {
  120. public:
  121. DemoMasterProcess (ChildProcessDemo& d) : demo (d), count (0) {}
  122. // This gets called when a message arrives from the slave process..
  123. void handleMessageFromSlave (const MemoryBlock& mb) override
  124. {
  125. ValueTree incomingMessage (memoryBlockToValueTree (mb));
  126. demo.logMessage ("Received: " + valueTreeToString (incomingMessage));
  127. }
  128. // This gets called if the slave process dies.
  129. void handleConnectionLost() override
  130. {
  131. demo.logMessage ("Connection lost to child process!");
  132. demo.killChildProcess();
  133. }
  134. void sendPingMessageToSlave()
  135. {
  136. ValueTree message ("MESSAGE");
  137. message.setProperty ("count", count++, nullptr);
  138. demo.logMessage ("Sending: " + valueTreeToString (message));
  139. sendMessageToSlave (valueTreeToMemoryBlock (message));
  140. }
  141. ChildProcessDemo& demo;
  142. int count;
  143. };
  144. //==============================================================================
  145. ScopedPointer<DemoMasterProcess> masterProcess;
  146. private:
  147. TextButton launchButton, pingButton, killButton;
  148. TextEditor testResultsBox;
  149. void buttonClicked (Button* button) override
  150. {
  151. if (button == &launchButton) launchChildProcess();
  152. if (button == &pingButton) pingChildProcess();
  153. if (button == &killButton) killChildProcess();
  154. }
  155. struct LogMessage : public Message
  156. {
  157. LogMessage (const String& m) : message (m) {}
  158. String message;
  159. };
  160. void handleMessage (const Message& message) override
  161. {
  162. testResultsBox.moveCaretToEnd();
  163. testResultsBox.insertTextAtCaret (static_cast<const LogMessage&> (message).message + newLine);
  164. testResultsBox.moveCaretToEnd();
  165. }
  166. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessDemo)
  167. };
  168. //==============================================================================
  169. /* This class gets instantiated in the child process, and receives messages from
  170. the master process.
  171. */
  172. class DemoSlaveProcess : public ChildProcessSlave,
  173. private DeletedAtShutdown
  174. {
  175. public:
  176. DemoSlaveProcess() {}
  177. void handleMessageFromMaster (const MemoryBlock& mb) override
  178. {
  179. ValueTree incomingMessage (memoryBlockToValueTree (mb));
  180. /* In the demo we're only expecting one type of message, which will contain a 'count' parameter -
  181. we'll just increment that number and send back a new message containing the new number.
  182. Obviously in a real app you'll probably want to look at the type of the message, and do
  183. some more interesting behaviour.
  184. */
  185. ValueTree reply ("REPLY");
  186. reply.setProperty ("countPlusOne", static_cast<int> (incomingMessage["count"]) + 1, nullptr);
  187. sendMessageToMaster (valueTreeToMemoryBlock (reply));
  188. }
  189. void handleConnectionMade() override
  190. {
  191. // This method is called when the connection is established, and in response, we'll just
  192. // send off a message to say hello.
  193. ValueTree reply ("HelloWorld");
  194. sendMessageToMaster (valueTreeToMemoryBlock (reply));
  195. }
  196. /* If no pings are received from the master process for a number of seconds, then this will get invoked.
  197. Typically you'll want to use this as a signal to kill the process as quickly as possible, as you
  198. don't want to leave it hanging around as a zombie..
  199. */
  200. void handleConnectionLost() override
  201. {
  202. JUCEApplication::quit();
  203. }
  204. };
  205. //==============================================================================
  206. /* The JuceDemoApplication::initialise method calls this function to allow the
  207. child process to launch when the command line parameters indicate that we're
  208. being asked to run as a child process..
  209. */
  210. bool invokeChildProcessDemo (const String& commandLine)
  211. {
  212. ScopedPointer<DemoSlaveProcess> slave (new DemoSlaveProcess());
  213. if (slave->initialiseFromCommandLine (commandLine, demoCommandLineUID))
  214. {
  215. slave.release(); // allow the slave object to stay alive - it'll handle its own deletion.
  216. return true;
  217. }
  218. return false;
  219. }
  220. // This static object will register this demo type in a global list of demos..
  221. static JuceDemoType<ChildProcessDemo> childProcessDemo ("40 Child Process Comms");
  222. #else
  223. // (Dummy stub for platforms that don't support this demo)
  224. bool invokeChildProcessDemo (const String&) { return false; }
  225. #endif