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.

282 lines
9.7KB

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