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.

280 lines
9.5KB

  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 MessageListener
  44. {
  45. public:
  46. ChildProcessDemo()
  47. {
  48. setOpaque (true);
  49. addAndMakeVisible (launchButton);
  50. launchButton.setButtonText ("Launch Child Process");
  51. launchButton.onClick = [this] { launchChildProcess(); };
  52. addAndMakeVisible (pingButton);
  53. pingButton.setButtonText ("Send Ping");
  54. pingButton.onClick = [this] { pingChildProcess(); };
  55. addAndMakeVisible (killButton);
  56. killButton.setButtonText ("Kill Child Process");
  57. killButton.onClick = [this] { killChildProcess(); };
  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.reset();
  67. }
  68. void paint (Graphics& g) override
  69. {
  70. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  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.reset();
  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. struct LogMessage : public Message
  149. {
  150. LogMessage (const String& m) : message (m) {}
  151. String message;
  152. };
  153. void handleMessage (const Message& message) override
  154. {
  155. testResultsBox.moveCaretToEnd();
  156. testResultsBox.insertTextAtCaret (static_cast<const LogMessage&> (message).message + newLine);
  157. testResultsBox.moveCaretToEnd();
  158. }
  159. void lookAndFeelChanged() override
  160. {
  161. testResultsBox.applyFontToAllText (testResultsBox.getFont());
  162. }
  163. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessDemo)
  164. };
  165. //==============================================================================
  166. /* This class gets instantiated in the child process, and receives messages from
  167. the master process.
  168. */
  169. class DemoSlaveProcess : public ChildProcessSlave,
  170. private DeletedAtShutdown
  171. {
  172. public:
  173. DemoSlaveProcess() {}
  174. void handleMessageFromMaster (const MemoryBlock& mb) override
  175. {
  176. ValueTree incomingMessage (memoryBlockToValueTree (mb));
  177. /* In the demo we're only expecting one type of message, which will contain a 'count' parameter -
  178. we'll just increment that number and send back a new message containing the new number.
  179. Obviously in a real app you'll probably want to look at the type of the message, and do
  180. some more interesting behaviour.
  181. */
  182. ValueTree reply ("REPLY");
  183. reply.setProperty ("countPlusOne", static_cast<int> (incomingMessage["count"]) + 1, nullptr);
  184. sendMessageToMaster (valueTreeToMemoryBlock (reply));
  185. }
  186. void handleConnectionMade() override
  187. {
  188. // This method is called when the connection is established, and in response, we'll just
  189. // send off a message to say hello.
  190. ValueTree reply ("HelloWorld");
  191. sendMessageToMaster (valueTreeToMemoryBlock (reply));
  192. }
  193. /* If no pings are received from the master process for a number of seconds, then this will get invoked.
  194. Typically you'll want to use this as a signal to kill the process as quickly as possible, as you
  195. don't want to leave it hanging around as a zombie..
  196. */
  197. void handleConnectionLost() override
  198. {
  199. JUCEApplication::quit();
  200. }
  201. };
  202. //==============================================================================
  203. /* The JuceDemoApplication::initialise method calls this function to allow the
  204. child process to launch when the command line parameters indicate that we're
  205. being asked to run as a child process..
  206. */
  207. bool invokeChildProcessDemo (const String& commandLine)
  208. {
  209. ScopedPointer<DemoSlaveProcess> slave (new DemoSlaveProcess());
  210. if (slave->initialiseFromCommandLine (commandLine, demoCommandLineUID))
  211. {
  212. slave.release(); // allow the slave object to stay alive - it'll handle its own deletion.
  213. return true;
  214. }
  215. return false;
  216. }
  217. // This static object will register this demo type in a global list of demos..
  218. static JuceDemoType<ChildProcessDemo> childProcessDemo ("40 Child Process Comms");
  219. #else
  220. // (Dummy stub for platforms that don't support this demo)
  221. bool invokeChildProcessDemo (const String&) { return false; }
  222. #endif