VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_extra / native / juce_linux_X11_WebBrowserComponent.cpp
blob57e7dec826db62c9549f9e50e1f826d1310bb4dc
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
23 ==============================================================================
26 namespace juce
29 //==============================================================================
30 class WebKitSymbols : public DeletedAtShutdown
32 public:
33 //==============================================================================
34 bool isWebKitAvailable() const noexcept { return webKitIsAvailable; }
36 //==============================================================================
37 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_new, juce_webkit_settings_new,
38 (), WebKitSettings*)
40 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_hardware_acceleration_policy, juce_webkit_settings_set_hardware_acceleration_policy,
41 (WebKitSettings*, int), void)
43 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_new_with_settings, juce_webkit_web_view_new_with_settings,
44 (WebKitSettings*), GtkWidget*)
46 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_load_uri, juce_webkit_web_view_load_uri,
47 (WebKitWebView*, const gchar*), void)
49 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_use, juce_webkit_policy_decision_use,
50 (WebKitPolicyDecision*), void)
52 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_policy_decision_ignore, juce_webkit_policy_decision_ignore,
53 (WebKitPolicyDecision*), void)
55 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_back, juce_webkit_web_view_go_back,
56 (WebKitWebView*), void)
58 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_go_forward, juce_webkit_web_view_go_forward,
59 (WebKitWebView*), void)
61 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_reload, juce_webkit_web_view_reload,
62 (WebKitWebView*), void)
64 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_stop_loading, juce_webkit_web_view_stop_loading,
65 (WebKitWebView*), void)
67 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_request_get_uri, juce_webkit_uri_request_get_uri,
68 (WebKitURIRequest*), const gchar*)
70 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_action_get_request, juce_webkit_navigation_action_get_request,
71 (WebKitNavigationAction*), WebKitURIRequest*)
73 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_frame_name, juce_webkit_navigation_policy_decision_get_frame_name,
74 (WebKitNavigationPolicyDecision*), const gchar*)
76 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_navigation_policy_decision_get_navigation_action, juce_webkit_navigation_policy_decision_get_navigation_action,
77 (WebKitNavigationPolicyDecision*), WebKitNavigationAction*)
79 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_uri, juce_webkit_web_view_get_uri,
80 (WebKitWebView*), const gchar*)
82 //==============================================================================
83 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_init, juce_gtk_init,
84 (int*, char***), void)
86 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_new, juce_gtk_plug_new,
87 (::Window), GtkWidget*)
89 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_scrolled_window_new, juce_gtk_scrolled_window_new,
90 (GtkAdjustment*, GtkAdjustment*), GtkWidget*)
92 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_container_add, juce_gtk_container_add,
93 (GtkContainer*, GtkWidget*), void)
95 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_widget_show_all, juce_gtk_widget_show_all,
96 (GtkWidget*), void)
98 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_plug_get_id, juce_gtk_plug_get_id,
99 (GtkPlug*), ::Window)
101 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main, juce_gtk_main,
102 (), void)
104 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_main_quit, juce_gtk_main_quit,
105 (), void)
107 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_unix_fd_add, juce_g_unix_fd_add,
108 (gint, GIOCondition, GUnixFDSourceFunc, gpointer), guint)
110 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_ref, juce_g_object_ref,
111 (gpointer), gpointer)
113 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_unref, juce_g_object_unref,
114 (gpointer), void)
116 JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_signal_connect_data, juce_g_signal_connect_data,
117 (gpointer, const gchar*, GCallback, gpointer, GClosureNotify, GConnectFlags), gulong)
119 //==============================================================================
120 JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WebKitSymbols)
122 private:
123 WebKitSymbols() = default;
125 ~WebKitSymbols()
127 clearSingletonInstance();
130 template <typename FuncPtr>
131 struct SymbolBinding
133 FuncPtr& func;
134 const char* name;
137 template <typename FuncPtr>
138 SymbolBinding<FuncPtr> makeSymbolBinding (FuncPtr& func, const char* name)
140 return { func, name };
143 template <typename FuncPtr>
144 bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding)
146 if (auto* func = lib.getFunction (binding.name))
148 binding.func = reinterpret_cast<FuncPtr> (func);
149 return true;
152 return false;
155 template <typename FuncPtr, typename... Args>
156 bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding, Args... args)
158 return loadSymbols (lib, binding) && loadSymbols (lib, args...);
161 //==============================================================================
162 bool loadWebkitSymbols()
164 return loadSymbols (webkitLib,
165 makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"),
166 makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"),
167 makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"),
168 makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"),
169 makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"),
170 makeSymbolBinding (juce_webkit_web_view_go_back, "webkit_web_view_go_back"),
171 makeSymbolBinding (juce_webkit_web_view_go_forward, "webkit_web_view_go_forward"),
172 makeSymbolBinding (juce_webkit_web_view_reload, "webkit_web_view_reload"),
173 makeSymbolBinding (juce_webkit_web_view_stop_loading, "webkit_web_view_stop_loading"),
174 makeSymbolBinding (juce_webkit_uri_request_get_uri, "webkit_uri_request_get_uri"),
175 makeSymbolBinding (juce_webkit_web_view_load_uri, "webkit_web_view_load_uri"),
176 makeSymbolBinding (juce_webkit_navigation_action_get_request, "webkit_navigation_action_get_request"),
177 makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name, "webkit_navigation_policy_decision_get_frame_name"),
178 makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"),
179 makeSymbolBinding (juce_webkit_web_view_get_uri, "webkit_web_view_get_uri"));
182 bool loadGtkSymbols()
184 return loadSymbols (gtkLib,
185 makeSymbolBinding (juce_gtk_init, "gtk_init"),
186 makeSymbolBinding (juce_gtk_plug_new, "gtk_plug_new"),
187 makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"),
188 makeSymbolBinding (juce_gtk_container_add, "gtk_container_add"),
189 makeSymbolBinding (juce_gtk_widget_show_all, "gtk_widget_show_all"),
190 makeSymbolBinding (juce_gtk_plug_get_id, "gtk_plug_get_id"),
191 makeSymbolBinding (juce_gtk_main, "gtk_main"),
192 makeSymbolBinding (juce_gtk_main_quit, "gtk_main_quit"),
193 makeSymbolBinding (juce_g_unix_fd_add, "g_unix_fd_add"),
194 makeSymbolBinding (juce_g_object_ref, "g_object_ref"),
195 makeSymbolBinding (juce_g_object_unref, "g_object_unref"),
196 makeSymbolBinding (juce_g_signal_connect_data, "g_signal_connect_data"));
199 //==============================================================================
200 DynamicLibrary gtkLib { "libgtk-3.so" }, webkitLib { "libwebkit2gtk-4.0.so" };
201 const bool webKitIsAvailable = loadWebkitSymbols() && loadGtkSymbols();
203 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebKitSymbols)
206 JUCE_IMPLEMENT_SINGLETON (WebKitSymbols)
208 //==============================================================================
209 extern int juce_gtkWebkitMain (int argc, const char* argv[]);
211 class CommandReceiver
213 public:
214 struct Responder
216 virtual ~Responder() {}
218 virtual void handleCommand (const String& cmd, const var& param) = 0;
219 virtual void receiverHadError() = 0;
222 CommandReceiver (Responder* responderToUse, int inputChannelToUse)
223 : responder (responderToUse), inChannel (inputChannelToUse)
225 setBlocking (inChannel, false);
228 static void setBlocking (int fd, bool shouldBlock)
230 auto flags = fcntl (fd, F_GETFL);
231 fcntl (fd, F_SETFL, (shouldBlock ? (flags & ~O_NONBLOCK)
232 : (flags | O_NONBLOCK)));
235 int getFd() const { return inChannel; }
237 void tryNextRead()
239 for (;;)
241 auto len = (receivingLength ? sizeof (size_t) : bufferLength.len);
243 if (! receivingLength)
244 buffer.realloc (len);
246 auto* dst = (receivingLength ? bufferLength.data : buffer.getData());
248 auto actual = read (inChannel, &dst[pos], static_cast<size_t> (len - pos));
250 if (actual < 0)
252 if (errno == EINTR)
253 continue;
255 break;
258 pos += static_cast<size_t> (actual);
260 if (pos == len)
262 pos = 0;
264 if (! receivingLength)
265 parseJSON (String (buffer.getData(), bufferLength.len));
267 receivingLength = (! receivingLength);
271 if (errno != EAGAIN && errno != EWOULDBLOCK && responder != nullptr)
272 responder->receiverHadError();
275 static void sendCommand (int outChannel, const String& cmd, const var& params)
277 DynamicObject::Ptr obj = new DynamicObject;
279 obj->setProperty (getCmdIdentifier(), cmd);
281 if (! params.isVoid())
282 obj->setProperty (getParamIdentifier(), params);
284 auto json = JSON::toString (var (obj.get()));
286 auto jsonLength = static_cast<size_t> (json.length());
287 auto len = sizeof (size_t) + jsonLength;
289 HeapBlock<char> buffer (len);
290 auto* dst = buffer.getData();
292 memcpy (dst, &jsonLength, sizeof (size_t));
293 dst += sizeof (size_t);
295 memcpy (dst, json.toRawUTF8(), jsonLength);
297 ssize_t ret;
299 for (;;)
301 ret = write (outChannel, buffer.getData(), len);
303 if (ret != -1 || errno != EINTR)
304 break;
308 private:
309 void parseJSON (const String& json)
311 auto object = JSON::fromString (json);
313 if (! object.isVoid())
315 auto cmd = object.getProperty (getCmdIdentifier(), {}).toString();
316 auto params = object.getProperty (getParamIdentifier(), {});
318 if (responder != nullptr)
319 responder->handleCommand (cmd, params);
323 static Identifier getCmdIdentifier() { static Identifier Id ("cmd"); return Id; }
324 static Identifier getParamIdentifier() { static Identifier Id ("params"); return Id; }
326 Responder* responder = nullptr;
327 int inChannel = 0;
328 size_t pos = 0;
329 bool receivingLength = true;
330 union { char data [sizeof (size_t)]; size_t len; } bufferLength;
331 HeapBlock<char> buffer;
334 #define juce_g_signal_connect(instance, detailed_signal, c_handler, data) \
335 WebKitSymbols::getInstance()->juce_g_signal_connect_data (instance, detailed_signal, c_handler, data, nullptr, (GConnectFlags) 0)
337 //==============================================================================
338 class GtkChildProcess : private CommandReceiver::Responder
340 public:
341 //==============================================================================
342 GtkChildProcess (int inChannel, int outChannelToUse)
343 : outChannel (outChannelToUse),
344 receiver (this, inChannel)
347 int entry()
349 CommandReceiver::setBlocking (outChannel, true);
351 WebKitSymbols::getInstance()->juce_gtk_init (nullptr, nullptr);
353 auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new();
354 WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings,
355 /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2);
357 auto* plug = WebKitSymbols::getInstance()->juce_gtk_plug_new (0);
358 auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr);
360 auto* webviewWidget = WebKitSymbols::getInstance()->juce_webkit_web_view_new_with_settings (settings);
361 webview = (WebKitWebView*) webviewWidget;
363 WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) container, webviewWidget);
364 WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) plug, container);
366 WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, "about:blank");
368 juce_g_signal_connect (webview, "decide-policy",
369 (GCallback) decidePolicyCallback, this);
371 juce_g_signal_connect (webview, "load-changed",
372 (GCallback) loadChangedCallback, this);
374 juce_g_signal_connect (webview, "load-failed",
375 (GCallback) loadFailedCallback, this);
377 WebKitSymbols::getInstance()->juce_gtk_widget_show_all (plug);
378 auto wID = (unsigned long) WebKitSymbols::getInstance()->juce_gtk_plug_get_id ((GtkPlug*) plug);
380 ssize_t ret;
382 for (;;)
384 ret = write (outChannel, &wID, sizeof (wID));
386 if (ret != -1 || errno != EINTR)
387 break;
390 WebKitSymbols::getInstance()->juce_g_unix_fd_add (receiver.getFd(), G_IO_IN, pipeReadyStatic, this);
391 receiver.tryNextRead();
393 WebKitSymbols::getInstance()->juce_gtk_main();
395 WebKitSymbols::getInstance()->deleteInstance();
396 return 0;
399 void goToURL (const var& params)
401 static Identifier urlIdentifier ("url");
402 auto url = params.getProperty (urlIdentifier, var()).toString();
404 WebKitSymbols::getInstance()->juce_webkit_web_view_load_uri (webview, url.toRawUTF8());
407 void handleDecisionResponse (const var& params)
409 auto* decision = (WebKitPolicyDecision*) ((int64) params.getProperty ("decision_id", var (0)));
410 bool allow = params.getProperty ("allow", var (false));
412 if (decision != nullptr && decisions.contains (decision))
414 if (allow)
415 WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
416 else
417 WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
419 decisions.removeAllInstancesOf (decision);
420 WebKitSymbols::getInstance()->juce_g_object_unref (decision);
424 //==============================================================================
425 void handleCommand (const String& cmd, const var& params) override
427 if (cmd == "quit") quit();
428 else if (cmd == "goToURL") goToURL (params);
429 else if (cmd == "goBack") WebKitSymbols::getInstance()->juce_webkit_web_view_go_back (webview);
430 else if (cmd == "goForward") WebKitSymbols::getInstance()->juce_webkit_web_view_go_forward (webview);
431 else if (cmd == "refresh") WebKitSymbols::getInstance()->juce_webkit_web_view_reload (webview);
432 else if (cmd == "stop") WebKitSymbols::getInstance()->juce_webkit_web_view_stop_loading (webview);
433 else if (cmd == "decision") handleDecisionResponse (params);
436 void receiverHadError() override
438 exit (-1);
441 //==============================================================================
442 bool pipeReady (gint fd, GIOCondition)
444 if (fd == receiver.getFd())
446 receiver.tryNextRead();
447 return true;
450 return false;
453 void quit()
455 WebKitSymbols::getInstance()->juce_gtk_main_quit();
458 String getURIStringForAction (WebKitNavigationAction* action)
460 auto* request = WebKitSymbols::getInstance()->juce_webkit_navigation_action_get_request (action);
461 return WebKitSymbols::getInstance()->juce_webkit_uri_request_get_uri (request);
464 bool onNavigation (String frameName,
465 WebKitNavigationAction* action,
466 WebKitPolicyDecision* decision)
468 if (decision != nullptr && frameName.isEmpty())
470 WebKitSymbols::getInstance()->juce_g_object_ref (decision);
471 decisions.add (decision);
473 DynamicObject::Ptr params = new DynamicObject;
475 params->setProperty ("url", getURIStringForAction (action));
476 params->setProperty ("decision_id", (int64) decision);
477 CommandReceiver::sendCommand (outChannel, "pageAboutToLoad", var (params.get()));
479 return true;
482 return false;
485 bool onNewWindow (String /*frameName*/,
486 WebKitNavigationAction* action,
487 WebKitPolicyDecision* decision)
489 if (decision != nullptr)
491 DynamicObject::Ptr params = new DynamicObject;
493 params->setProperty ("url", getURIStringForAction (action));
494 CommandReceiver::sendCommand (outChannel, "newWindowAttemptingToLoad", var (params.get()));
496 // never allow new windows
497 WebKitSymbols::getInstance()->juce_webkit_policy_decision_ignore (decision);
499 return true;
502 return false;
505 void onLoadChanged (WebKitLoadEvent loadEvent)
507 if (loadEvent == WEBKIT_LOAD_FINISHED)
509 DynamicObject::Ptr params = new DynamicObject;
511 params->setProperty ("url", String (WebKitSymbols::getInstance()->juce_webkit_web_view_get_uri (webview)));
512 CommandReceiver::sendCommand (outChannel, "pageFinishedLoading", var (params.get()));
516 bool onDecidePolicy (WebKitPolicyDecision* decision,
517 WebKitPolicyDecisionType decisionType)
519 switch (decisionType)
521 case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
523 auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
524 auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
526 return onNavigation (String (frameName != nullptr ? frameName : ""),
527 WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
528 decision);
530 break;
531 case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
533 auto* navigationDecision = (WebKitNavigationPolicyDecision*) decision;
534 auto* frameName = WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_frame_name (navigationDecision);
536 return onNewWindow (String (frameName != nullptr ? frameName : ""),
537 WebKitSymbols::getInstance()->juce_webkit_navigation_policy_decision_get_navigation_action (navigationDecision),
538 decision);
540 break;
541 case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
543 auto* response = (WebKitNavigationPolicyDecision*) decision;
545 // for now just always allow response requests
546 ignoreUnused (response);
547 WebKitSymbols::getInstance()->juce_webkit_policy_decision_use (decision);
548 return true;
550 break;
551 default:
552 break;
555 return false;
558 void onLoadFailed (GError* error)
560 DynamicObject::Ptr params = new DynamicObject;
562 params->setProperty ("error", String (error != nullptr ? error->message : "unknown error"));
563 CommandReceiver::sendCommand (outChannel, "pageLoadHadNetworkError", var (params.get()));
566 private:
567 static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user)
569 return (reinterpret_cast<GtkChildProcess*> (user)->pipeReady (fd, condition) ? TRUE : FALSE);
572 static gboolean decidePolicyCallback (WebKitWebView*,
573 WebKitPolicyDecision* decision,
574 WebKitPolicyDecisionType decisionType,
575 gpointer user)
577 auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
578 return (owner.onDecidePolicy (decision, decisionType) ? TRUE : FALSE);
581 static void loadChangedCallback (WebKitWebView*,
582 WebKitLoadEvent loadEvent,
583 gpointer user)
585 auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
586 owner.onLoadChanged (loadEvent);
589 static void loadFailedCallback (WebKitWebView*,
590 WebKitLoadEvent /*loadEvent*/,
591 gchar* /*failing_uri*/,
592 GError* error,
593 gpointer user)
595 auto& owner = *reinterpret_cast<GtkChildProcess*> (user);
596 owner.onLoadFailed (error);
599 int outChannel = 0;
600 CommandReceiver receiver;
601 WebKitWebView* webview = nullptr;
602 Array<WebKitPolicyDecision*> decisions;
605 //==============================================================================
606 class WebBrowserComponent::Pimpl : private Thread,
607 private CommandReceiver::Responder
609 public:
610 Pimpl (WebBrowserComponent& parent)
611 : Thread ("Webview"), owner (parent)
613 webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable();
616 ~Pimpl() override
618 quit();
621 //==============================================================================
622 void init()
624 if (! webKitIsAvailable)
625 return;
627 launchChild();
629 auto ret = pipe (threadControl);
631 ignoreUnused (ret);
632 jassert (ret == 0);
634 CommandReceiver::setBlocking (inChannel, true);
635 CommandReceiver::setBlocking (outChannel, true);
636 CommandReceiver::setBlocking (threadControl[0], false);
637 CommandReceiver::setBlocking (threadControl[1], true);
639 unsigned long windowHandle;
640 auto actual = read (inChannel, &windowHandle, sizeof (windowHandle));
642 if (actual != (ssize_t) sizeof (windowHandle))
644 killChild();
645 return;
648 receiver.reset (new CommandReceiver (this, inChannel));
650 pfds.push_back ({ threadControl[0], POLLIN, 0 });
651 pfds.push_back ({ receiver->getFd(), POLLIN, 0 });
653 startThread();
655 xembed.reset (new XEmbedComponent (windowHandle));
656 owner.addAndMakeVisible (xembed.get());
659 void quit()
661 if (! webKitIsAvailable)
662 return;
664 if (isThreadRunning())
666 signalThreadShouldExit();
668 char ignore = 0;
669 ssize_t ret;
671 for (;;)
673 ret = write (threadControl[1], &ignore, 1);
675 if (ret != -1 || errno != EINTR)
676 break;
679 waitForThreadToExit (-1);
680 receiver = nullptr;
683 if (childProcess != 0)
685 CommandReceiver::sendCommand (outChannel, "quit", {});
686 killChild();
690 //==============================================================================
691 void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData)
693 if (! webKitIsAvailable)
694 return;
696 DynamicObject::Ptr params = new DynamicObject;
698 params->setProperty ("url", url);
700 if (headers != nullptr)
701 params->setProperty ("headers", var (*headers));
703 if (postData != nullptr)
704 params->setProperty ("postData", var (*postData));
706 CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get()));
709 void goBack() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); }
710 void goForward() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); }
711 void refresh() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); }
712 void stop() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); }
714 void resized()
716 if (xembed != nullptr)
717 xembed->setBounds (owner.getLocalBounds());
720 private:
721 //==============================================================================
722 void killChild()
724 if (childProcess != 0)
726 xembed = nullptr;
728 int status = 0, result = 0;
730 result = waitpid (childProcess, &status, WNOHANG);
731 for (int i = 0; i < 15 && (! WIFEXITED(status) || result != childProcess); ++i)
733 Thread::sleep (100);
734 result = waitpid (childProcess, &status, WNOHANG);
737 // clean-up any zombies
738 status = 0;
739 if (! WIFEXITED(status) || result != childProcess)
741 for (;;)
743 kill (childProcess, SIGTERM);
744 waitpid (childProcess, &status, 0);
746 if (WIFEXITED (status))
747 break;
751 childProcess = 0;
755 void launchChild()
757 int inPipe[2], outPipe[2];
759 auto ret = pipe (inPipe);
760 ignoreUnused (ret); jassert (ret == 0);
762 ret = pipe (outPipe);
763 ignoreUnused (ret); jassert (ret == 0);
765 auto pid = fork();
766 if (pid == 0)
768 close (inPipe[0]);
769 close (outPipe[1]);
771 HeapBlock<const char*> argv (5);
772 StringArray arguments;
774 arguments.add (File::getSpecialLocation (File::currentExecutableFile).getFullPathName());
775 arguments.add ("--juce-gtkwebkitfork-child");
776 arguments.add (String (outPipe[0]));
777 arguments.add (String (inPipe [1]));
779 for (int i = 0; i < arguments.size(); ++i)
780 argv[i] = arguments[i].toRawUTF8();
782 argv[4] = nullptr;
784 #if JUCE_STANDALONE_APPLICATION
785 execv (arguments[0].toRawUTF8(), (char**) argv.getData());
786 #else
787 juce_gtkWebkitMain (4, (const char**) argv.getData());
788 #endif
789 exit (0);
792 close (inPipe[1]);
793 close (outPipe[0]);
795 inChannel = inPipe[0];
796 outChannel = outPipe[1];
798 childProcess = pid;
801 void run() override
803 while (! threadShouldExit())
805 if (shouldExit())
806 return;
808 receiver->tryNextRead();
810 int result = 0;
812 while (result == 0 || (result < 0 && errno == EINTR))
813 result = poll (&pfds.front(), static_cast<nfds_t> (pfds.size()), 0);
815 if (result < 0)
816 break;
820 bool shouldExit()
822 char ignore;
823 auto result = read (threadControl[0], &ignore, 1);
825 return (result != -1 || (errno != EAGAIN && errno != EWOULDBLOCK));
828 //==============================================================================
829 void handleCommandOnMessageThread (const String& cmd, const var& params)
831 auto url = params.getProperty ("url", var()).toString();
833 if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params);
834 else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url);
835 else if (cmd == "windowCloseRequest") owner.windowCloseRequest();
836 else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url);
837 else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params);
839 threadBlocker.signal();
842 void handlePageAboutToLoad (const String& url, const var& inputParams)
844 int64 decision_id = inputParams.getProperty ("decision_id", var (0));
846 if (decision_id != 0)
848 DynamicObject::Ptr params = new DynamicObject;
850 params->setProperty ("decision_id", decision_id);
851 params->setProperty ("allow", owner.pageAboutToLoad (url));
853 CommandReceiver::sendCommand (outChannel, "decision", var (params.get()));
857 void handlePageLoadHadNetworkError (const var& params)
859 String error = params.getProperty ("error", "Unknown error");
861 if (owner.pageLoadHadNetworkError (error))
862 goToURL (String ("data:text/plain,") + error, nullptr, nullptr);
865 void handleCommand (const String& cmd, const var& params) override
867 threadBlocker.reset();
869 (new HandleOnMessageThread (this, cmd, params))->post();
871 // wait until the command has executed on the message thread
872 // this ensures that Pimpl can never be deleted while the
873 // message has not been executed yet
874 threadBlocker.wait (-1);
877 void receiverHadError() override {}
879 //==============================================================================
880 struct HandleOnMessageThread : public CallbackMessage
882 HandleOnMessageThread (Pimpl* pimpl, const String& cmdToUse, const var& params)
883 : owner (pimpl), cmdToSend (cmdToUse), paramsToSend (params)
886 void messageCallback() override
888 owner->handleCommandOnMessageThread (cmdToSend, paramsToSend);
891 Pimpl* owner = nullptr;
892 String cmdToSend;
893 var paramsToSend;
896 bool webKitIsAvailable = false;
898 WebBrowserComponent& owner;
899 std::unique_ptr<CommandReceiver> receiver;
900 int childProcess = 0, inChannel = 0, outChannel = 0;
901 int threadControl[2];
902 std::unique_ptr<XEmbedComponent> xembed;
903 WaitableEvent threadBlocker;
904 std::vector<pollfd> pfds;
907 //==============================================================================
908 WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden)
909 : browser (new Pimpl (*this)),
910 unloadPageWhenHidden (unloadWhenHidden)
912 ignoreUnused (blankPageShown);
913 ignoreUnused (unloadPageWhenHidden);
915 setOpaque (true);
917 browser->init();
920 WebBrowserComponent::~WebBrowserComponent()
924 //==============================================================================
925 void WebBrowserComponent::goToURL (const String& url,
926 const StringArray* headers,
927 const MemoryBlock* postData)
929 lastURL = url;
931 if (headers != nullptr)
932 lastHeaders = *headers;
933 else
934 lastHeaders.clear();
936 if (postData != nullptr)
937 lastPostData = *postData;
938 else
939 lastPostData.reset();
941 browser->goToURL (url, headers, postData);
944 void WebBrowserComponent::stop()
946 browser->stop();
949 void WebBrowserComponent::goBack()
951 lastURL.clear();
953 browser->goBack();
956 void WebBrowserComponent::goForward()
958 lastURL.clear();
959 browser->goForward();
962 void WebBrowserComponent::refresh()
964 browser->refresh();
967 //==============================================================================
968 void WebBrowserComponent::paint (Graphics& g)
970 g.fillAll (Colours::white);
973 void WebBrowserComponent::checkWindowAssociation()
977 void WebBrowserComponent::reloadLastURL()
979 if (lastURL.isNotEmpty())
981 goToURL (lastURL, &lastHeaders, &lastPostData);
982 lastURL.clear();
986 void WebBrowserComponent::parentHierarchyChanged()
988 checkWindowAssociation();
991 void WebBrowserComponent::resized()
993 if (browser != nullptr)
994 browser->resized();
997 void WebBrowserComponent::visibilityChanged()
999 checkWindowAssociation();
1002 void WebBrowserComponent::focusGained (FocusChangeType)
1006 void WebBrowserComponent::clearCookies()
1008 // Currently not implemented on linux as WebBrowserComponent currently does not
1009 // store cookies on linux
1010 jassertfalse;
1013 int juce_gtkWebkitMain (int argc, const char* argv[])
1015 if (argc != 4)
1016 return -1;
1018 GtkChildProcess child (String (argv[2]).getIntValue(),
1019 String (argv[3]).getIntValue());
1021 return child.entry();
1024 } // namespace juce