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.

813 lines
25KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. //==============================================================================
  18. extern int juce_gtkWebkitMain (int argc, const char* argv[]);
  19. class CommandReceiver
  20. {
  21. public:
  22. struct Responder
  23. {
  24. virtual ~Responder() {}
  25. virtual void handleCommand (const String& cmd, const var& param) = 0;
  26. virtual void receiverHadError() = 0;
  27. };
  28. CommandReceiver (Responder* responderToUse, int inputChannelToUse)
  29. : responder (responderToUse), inChannel (inputChannelToUse)
  30. {
  31. setBlocking (inChannel, false);
  32. }
  33. static void setBlocking (int fd, bool shouldBlock)
  34. {
  35. int flags = fcntl (fd, F_GETFL);
  36. fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
  37. : (flags | O_NONBLOCK)));
  38. }
  39. int getFd() const { return inChannel; }
  40. void tryNextRead()
  41. {
  42. while (true)
  43. {
  44. size_t len = (receivingLength ? sizeof (size_t) : bufferLength.len);
  45. if (! receivingLength)
  46. buffer.realloc (len);
  47. char* dst = (receivingLength ? bufferLength.data : buffer.getData());
  48. ssize_t actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
  49. if (actual < 0)
  50. {
  51. if (errno == EINTR)
  52. continue;
  53. break;
  54. }
  55. pos += static_cast<size_t> (actual);
  56. if (pos == len)
  57. {
  58. pos = 0;
  59. if (! receivingLength)
  60. parseJSON (String (buffer.getData(), bufferLength.len));
  61. receivingLength = (! receivingLength);
  62. }
  63. }
  64. if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
  65. responder->receiverHadError();
  66. }
  67. static void sendCommand (int outChannel, const String& cmd, const var& params)
  68. {
  69. DynamicObject::Ptr obj = new DynamicObject;
  70. obj->setProperty (getCmdIdentifier(), cmd);
  71. if (! params.isVoid())
  72. obj->setProperty (getParamIdentifier(), params);
  73. String json (JSON::toString (var (obj)));
  74. size_t jsonLength = static_cast<size_t> (json.length());
  75. size_t len = sizeof (size_t) + jsonLength;
  76. HeapBlock<char> buffer (len);
  77. char* dst = buffer.getData();
  78. memcpy (dst, &jsonLength, sizeof (size_t));
  79. dst += sizeof (size_t);
  80. memcpy (dst, json.toRawUTF8(), jsonLength);
  81. ssize_t ret;
  82. do
  83. {
  84. ret = write (outChannel, buffer.getData(), len);
  85. } while (ret == -1 && errno == EINTR);
  86. }
  87. private:
  88. void parseJSON (const String& json)
  89. {
  90. var object (JSON::fromString (json));
  91. if (! object.isVoid())
  92. {
  93. String cmd (object.getProperty (getCmdIdentifier(), var()).toString());
  94. var params (object.getProperty (getParamIdentifier(), var()));
  95. if (responder != nullptr)
  96. responder->handleCommand (cmd, params);
  97. }
  98. }
  99. static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
  100. static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
  101. Responder* responder;
  102. int inChannel;
  103. size_t pos = 0;
  104. bool receivingLength = true;
  105. union { char data [sizeof (size_t)]; size_t len; } bufferLength;
  106. HeapBlock<char> buffer;
  107. };
  108. //==============================================================================
  109. class GtkChildProcess : private CommandReceiver::Responder
  110. {
  111. public:
  112. //==============================================================================
  113. GtkChildProcess (int inChannel, int outChannelToUse)
  114. : outChannel (outChannelToUse), receiver (this, inChannel)
  115. {}
  116. int entry()
  117. {
  118. CommandReceiver::setBlocking (outChannel, true);
  119. gtk_init (nullptr, nullptr);
  120. WebKitSettings* settings = webkit_settings_new();
  121. webkit_settings_set_hardware_acceleration_policy (settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER);
  122. GtkWidget *plug;
  123. plug = gtk_plug_new(0);
  124. GtkWidget* container;
  125. container = gtk_scrolled_window_new (nullptr, nullptr);
  126. GtkWidget* webviewWidget = webkit_web_view_new_with_settings (settings);
  127. webview = WEBKIT_WEB_VIEW (webviewWidget);
  128. gtk_container_add (GTK_CONTAINER (container), webviewWidget);
  129. gtk_container_add (GTK_CONTAINER (plug), container);
  130. webkit_web_view_load_uri (webview, "about:blank");
  131. g_signal_connect (webview, "decide-policy",
  132. G_CALLBACK (decidePolicyCallback), this);
  133. g_signal_connect (webview, "load-changed",
  134. G_CALLBACK (loadChangedCallback), this);
  135. g_signal_connect (webview, "load-failed",
  136. G_CALLBACK (loadFailedCallback), this);
  137. gtk_widget_show_all (plug);
  138. unsigned long wID = (unsigned long) gtk_plug_get_id (GTK_PLUG (plug));
  139. ssize_t ret;
  140. do {
  141. ret = write (outChannel, &wID, sizeof (wID));
  142. } while (ret == -1 && errno == EINTR);
  143. g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
  144. receiver.tryNextRead();
  145. gtk_main();
  146. return 0;
  147. }
  148. void goToURL (const var& params)
  149. {
  150. static Identifier urlIdentifier ("url");
  151. String url (params.getProperty (urlIdentifier, var()).toString());
  152. webkit_web_view_load_uri (webview, url.toRawUTF8());
  153. }
  154. void handleDecisionResponse (const var& params)
  155. {
  156. WebKitPolicyDecision* decision
  157. = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
  158. bool allow = params.getProperty ("allow", var (false));
  159. if (decision != nullptr && decisions.contains (decision))
  160. {
  161. if (allow)
  162. webkit_policy_decision_use (decision);
  163. else
  164. webkit_policy_decision_ignore (decision);
  165. decisions.removeAllInstancesOf (decision);
  166. g_object_unref (decision);
  167. }
  168. }
  169. //==============================================================================
  170. void handleCommand (const String& cmd, const var& params) override
  171. {
  172. if (cmd == "quit") quit();
  173. else if (cmd == "goToURL") goToURL (params);
  174. else if (cmd == "goBack") webkit_web_view_go_back (webview);
  175. else if (cmd == "goForward") webkit_web_view_go_forward (webview);
  176. else if (cmd == "refresh") webkit_web_view_reload (webview);
  177. else if (cmd == "stop") webkit_web_view_stop_loading (webview);
  178. else if (cmd == "decision") handleDecisionResponse (params);
  179. }
  180. void receiverHadError() override
  181. {
  182. exit (-1);
  183. }
  184. //==============================================================================
  185. bool pipeReady (gint fd, GIOCondition)
  186. {
  187. if (fd == receiver.getFd())
  188. {
  189. receiver.tryNextRead();
  190. return true;
  191. }
  192. return false;
  193. }
  194. void quit()
  195. {
  196. gtk_main_quit();
  197. }
  198. bool onNavigation (String frameName,
  199. WebKitNavigationAction* action,
  200. WebKitPolicyDecision* decision)
  201. {
  202. if (decision != nullptr && frameName.isEmpty())
  203. {
  204. g_object_ref (decision);
  205. decisions.add (decision);
  206. DynamicObject::Ptr params = new DynamicObject;
  207. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  208. params->setProperty ("decision_id", (int64) decision);
  209. CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params));
  210. return true;
  211. }
  212. return false;
  213. }
  214. bool onNewWindow (String /*frameName*/,
  215. WebKitNavigationAction* action,
  216. WebKitPolicyDecision* decision)
  217. {
  218. if (decision != nullptr)
  219. {
  220. DynamicObject::Ptr params = new DynamicObject;
  221. params->setProperty ("url", String (webkit_uri_request_get_uri (webkit_navigation_action_get_request (action))));
  222. CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params));
  223. // never allow new windows
  224. webkit_policy_decision_ignore (decision);
  225. return true;
  226. }
  227. return false;
  228. }
  229. void onLoadChanged (WebKitLoadEvent loadEvent)
  230. {
  231. if (loadEvent == WEBKIT_LOAD_FINISHED)
  232. {
  233. DynamicObject::Ptr params = new DynamicObject;
  234. params->setProperty ("url", String (webkit_web_view_get_uri (webview)));
  235. CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params));
  236. }
  237. }
  238. bool onDecidePolicy (WebKitPolicyDecision* decision,
  239. WebKitPolicyDecisionType decisionType)
  240. {
  241. switch (decisionType)
  242. {
  243. case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
  244. {
  245. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  246. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  247. return onNavigation (String (frameName != nullptr ? frameName : ""),
  248. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  249. decision);
  250. }
  251. break;
  252. case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
  253. {
  254. WebKitNavigationPolicyDecision* navigationDecision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
  255. const char* frameName = webkit_navigation_policy_decision_get_frame_name (navigationDecision);
  256. return onNewWindow (String (frameName != nullptr ? frameName : ""),
  257. webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
  258. decision);
  259. }
  260. break;
  261. case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
  262. {
  263. WebKitResponsePolicyDecision *response = WEBKIT_RESPONSE_POLICY_DECISION (decision);
  264. // for now just always allow response requests
  265. ignoreUnused (response);
  266. webkit_policy_decision_use (decision);
  267. return true;
  268. }
  269. break;
  270. default:
  271. break;
  272. }
  273. return false;
  274. }
  275. void onLoadFailed (GError* error)
  276. {
  277. DynamicObject::Ptr params = new DynamicObject;
  278. params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
  279. CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params));
  280. }
  281. private:
  282. static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
  283. {
  284. return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
  285. }
  286. static gboolean decidePolicyCallback (WebKitWebView*,
  287. WebKitPolicyDecision* decision,
  288. WebKitPolicyDecisionType decisionType,
  289. gpointer user)
  290. {
  291. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  292. return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
  293. }
  294. static void loadChangedCallback (WebKitWebView*,
  295. WebKitLoadEvent loadEvent,
  296. gpointer user)
  297. {
  298. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  299. owner.onLoadChanged (loadEvent);
  300. }
  301. static void loadFailedCallback (WebKitWebView*,
  302. WebKitLoadEvent /*loadEvent*/,
  303. gchar* /*failing_uri*/,
  304. GError* error,
  305. gpointer user)
  306. {
  307. GtkChildProcess& owner = *reinterpret_cast<GtkChildProcess*> (user);
  308. owner.onLoadFailed (error);
  309. }
  310. int outChannel;
  311. CommandReceiver receiver;
  312. WebKitWebView* webview = nullptr;
  313. Array<WebKitPolicyDecision*> decisions;
  314. };
  315. //==============================================================================
  316. class WebBrowserComponent::Pimpl : private Thread, private CommandReceiver::Responder
  317. {
  318. public:
  319. Pimpl (WebBrowserComponent& parent)
  320. : Thread ("Webview"), owner (parent)
  321. {}
  322. ~Pimpl()
  323. {
  324. quit();
  325. }
  326. //==============================================================================
  327. void init()
  328. {
  329. launchChild();
  330. int ret = pipe (threadControl);
  331. ignoreUnused (ret);
  332. jassert (ret == 0);
  333. CommandReceiver::setBlocking (inChannel, true);
  334. CommandReceiver::setBlocking (outChannel, true);
  335. CommandReceiver::setBlocking (threadControl[0], false);
  336. CommandReceiver::setBlocking (threadControl[1], true);
  337. unsigned long windowHandle;
  338. ssize_t actual = read (inChannel, &windowHandle, sizeof (windowHandle));
  339. if (actual != sizeof (windowHandle))
  340. {
  341. killChild();
  342. return;
  343. }
  344. receiver = new CommandReceiver (this, inChannel);
  345. startThread();
  346. xembed = new XEmbedComponent (windowHandle);
  347. owner.addAndMakeVisible (xembed);
  348. }
  349. void quit()
  350. {
  351. if (isThreadRunning())
  352. {
  353. signalThreadShouldExit();
  354. char ignore = 0;
  355. ssize_t ret;
  356. do
  357. {
  358. ret = write (threadControl[1], &ignore, 1);
  359. } while (ret == -1 && errno == EINTR);
  360. waitForThreadToExit (-1);
  361. receiver = nullptr;
  362. }
  363. if (childProcess != 0)
  364. {
  365. CommandReceiver::sendCommand (outChannel, "quit", var());
  366. killChild();
  367. }
  368. }
  369. //==============================================================================
  370. void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
  371. {
  372. DynamicObject::Ptr params = new DynamicObject;
  373. params->setProperty ("url", url);
  374. if (headers != nullptr)
  375. params->setProperty ("headers", var (*headers));
  376. if (postData != nullptr)
  377. params->setProperty ("postData", var (*postData));
  378. CommandReceiver::sendCommand (outChannel, "goToURL", var (params));
  379. }
  380. void goBack() { CommandReceiver::sendCommand (outChannel, "goBack", var()); }
  381. void goForward() { CommandReceiver::sendCommand (outChannel, "goForward", var()); }
  382. void refresh() { CommandReceiver::sendCommand (outChannel, "refresh", var()); }
  383. void stop() { CommandReceiver::sendCommand (outChannel, "stop", var()); }
  384. void resized()
  385. {
  386. if (xembed != nullptr)
  387. xembed->setBounds (owner.getLocalBounds());
  388. }
  389. private:
  390. //==============================================================================
  391. void killChild()
  392. {
  393. if (childProcess != 0)
  394. {
  395. xembed = nullptr;
  396. int status = 0, result;
  397. result = waitpid (childProcess, &status, WNOHANG);
  398. for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
  399. {
  400. Thread::sleep (100);
  401. result = waitpid (childProcess, &status, WNOHANG);
  402. }
  403. // clean-up any zombies
  404. status = 0;
  405. if (! WIFEXITED(status) || result != childProcess)
  406. {
  407. do
  408. {
  409. kill (childProcess, SIGTERM);
  410. waitpid (childProcess, &status, 0);
  411. } while (! WIFEXITED(status));
  412. }
  413. childProcess = 0;
  414. }
  415. }
  416. void launchChild()
  417. {
  418. int ret;
  419. int inPipe[2], outPipe[2];
  420. ret = pipe (inPipe);
  421. ignoreUnused (ret); jassert (ret == 0);
  422. ret = pipe (outPipe);
  423. ignoreUnused (ret); jassert (ret == 0);
  424. int pid = fork();
  425. if (pid == 0)
  426. {
  427. close (inPipe[0]);
  428. close (outPipe[1]);
  429. HeapBlock<const char*> argv (5);
  430. StringArray arguments;
  431. arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
  432. arguments.add ("--juce-gtkwebkitfork-child");
  433. arguments.add (String (outPipe[0]));
  434. arguments.add (String (inPipe [1]));
  435. for (int i = 0; i < arguments.size(); ++i)
  436. argv[i] = arguments[i].toRawUTF8();
  437. argv[4] = nullptr;
  438. #if JUCE_STANDALONE_APPLICATION
  439. execv (arguments[0].toRawUTF8(), (char**) argv.getData());
  440. #else
  441. juce_gtkWebkitMain (4, (const char**) argv.getData());
  442. #endif
  443. exit (0);
  444. }
  445. close (inPipe[1]);
  446. close (outPipe[0]);
  447. inChannel = inPipe[0];
  448. outChannel = outPipe[1];
  449. childProcess = pid;
  450. }
  451. void run() override
  452. {
  453. while (! threadShouldExit())
  454. {
  455. if (shouldExit())
  456. return;
  457. receiver->tryNextRead();
  458. fd_set set;
  459. FD_ZERO (&set);
  460. FD_SET (threadControl[0], &set);
  461. FD_SET (receiver->getFd(), &set);
  462. int max_fd = jmax (threadControl[0], receiver->getFd());
  463. int result = 0;
  464. while (result == 0 || (result < 0 && errno == EINTR))
  465. result = select (max_fd + 1, &set, NULL, NULL, NULL);
  466. if (result < 0)
  467. break;
  468. }
  469. }
  470. bool shouldExit()
  471. {
  472. char ignore;
  473. ssize_t result = read (threadControl[0], &ignore, 1);
  474. return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
  475. }
  476. //==============================================================================
  477. void handleCommandOnMessageThread (const String& cmd, const var& params)
  478. {
  479. String url (params.getProperty ("url", var()).toString());
  480. if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
  481. else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
  482. else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
  483. else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
  484. else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
  485. threadBlocker.signal();
  486. }
  487. void handlePageAboutToLoad (const String& url, const var& inputParams)
  488. {
  489. int64 decision_id = inputParams.getProperty ("decision_id", var (0));
  490. if (decision_id != 0)
  491. {
  492. DynamicObject::Ptr params = new DynamicObject;
  493. params->setProperty ("decision_id", decision_id);
  494. params->setProperty ("allow", owner.pageAboutToLoad (url));
  495. CommandReceiver::sendCommand (outChannel, "decision", var (params));
  496. }
  497. }
  498. void handlePageLoadHadNetworkError (const var& params)
  499. {
  500. String error = params.getProperty ("error", "Unknown error");
  501. if (owner.pageLoadHadNetworkError (error))
  502. goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
  503. }
  504. void handleCommand (const String& cmd, const var& params) override
  505. {
  506. threadBlocker.reset();
  507. (new HandleOnMessageThread (this, cmd, params))->post();
  508. // wait until the command has executed on the message thread
  509. // this ensures that Pimpl can never be deleted while the
  510. // message has not been executed yet
  511. threadBlocker.wait (-1);
  512. }
  513. void receiverHadError() override {}
  514. //==============================================================================
  515. struct HandleOnMessageThread : public CallbackMessage
  516. {
  517. HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
  518. : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
  519. {}
  520. void messageCallback() override
  521. {
  522. owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
  523. }
  524. Pimpl* owner;
  525. String cmdToSend;
  526. var paramsToSend;
  527. };
  528. private:
  529. WebBrowserComponent& owner;
  530. ScopedPointer<CommandReceiver> receiver;
  531. int childProcess = 0, inChannel = 0, outChannel = 0;
  532. int threadControl[2];
  533. ScopedPointer<XEmbedComponent> xembed;
  534. WaitableEvent threadBlocker;
  535. };
  536. //==============================================================================
  537. WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_)
  538. : browser (new Pimpl (*this)),
  539. blankPageShown (false),
  540. unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_)
  541. {
  542. setOpaque (true);
  543. browser->init();
  544. }
  545. WebBrowserComponent::~WebBrowserComponent()
  546. {
  547. if (browser != nullptr)
  548. {
  549. delete browser;
  550. browser = nullptr;
  551. }
  552. }
  553. //==============================================================================
  554. void WebBrowserComponent::goToURL (const String& url,
  555. const StringArray* headers,
  556. const MemoryBlock* postData)
  557. {
  558. lastURL = url;
  559. if (headers != nullptr)
  560. lastHeaders = *headers;
  561. else
  562. lastHeaders.clear();
  563. if (postData != nullptr)
  564. lastPostData = *postData;
  565. else
  566. lastPostData.reset();
  567. blankPageShown = false;
  568. browser->goToURL (url, headers, postData);
  569. }
  570. void WebBrowserComponent::stop()
  571. {
  572. browser->stop();
  573. }
  574. void WebBrowserComponent::goBack()
  575. {
  576. lastURL.clear();
  577. blankPageShown = false;
  578. browser->goBack();
  579. }
  580. void WebBrowserComponent::goForward()
  581. {
  582. lastURL.clear();
  583. browser->goForward();
  584. }
  585. void WebBrowserComponent::refresh()
  586. {
  587. browser->refresh();
  588. }
  589. //==============================================================================
  590. void WebBrowserComponent::paint (Graphics& g)
  591. {
  592. g.fillAll (Colours::white);
  593. }
  594. void WebBrowserComponent::checkWindowAssociation()
  595. {
  596. }
  597. void WebBrowserComponent::reloadLastURL()
  598. {
  599. if (lastURL.isNotEmpty())
  600. {
  601. goToURL (lastURL, &lastHeaders, &lastPostData);
  602. lastURL.clear();
  603. }
  604. }
  605. void WebBrowserComponent::parentHierarchyChanged()
  606. {
  607. checkWindowAssociation();
  608. }
  609. void WebBrowserComponent::resized()
  610. {
  611. if (browser != nullptr)
  612. browser->resized();
  613. }
  614. void WebBrowserComponent::visibilityChanged()
  615. {
  616. checkWindowAssociation();
  617. }
  618. void WebBrowserComponent::focusGained (FocusChangeType)
  619. {
  620. }
  621. void WebBrowserComponent::clearCookies()
  622. {
  623. // Currently not implemented on linux as WebBrowserComponent currently does not
  624. // store cookies on linux
  625. jassertfalse;
  626. }
  627. int juce_gtkWebkitMain (int argc, const char* argv[])
  628. {
  629. if (argc != 4) return -1;
  630. GtkChildProcess child (String (argv[2]).getIntValue(),
  631. String (argv[3]).getIntValue());
  632. return child.entry();
  633. }