DISTRHO Plugin Framework
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.

1555 lines
53KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #if !defined(DISTRHO_WEB_VIEW_HPP_INCLUDED) && !defined(DGL_WEB_VIEW_HPP_INCLUDED)
  17. # error bad include
  18. #endif
  19. #if !defined(WEB_VIEW_DISTRHO_NAMESPACE) && !defined(WEB_VIEW_DGL_NAMESPACE)
  20. # error bad usage
  21. #endif
  22. // #include <gtk/gtk.h>
  23. // #include <gtk/gtkx.h>
  24. // #include <webkit2/webkit2.h>
  25. #define WEB_VIEW_USING_CHOC 0
  26. #ifndef WEB_VIEW_USING_CHOC
  27. # define WEB_VIEW_USING_CHOC 0
  28. #elif WEB_VIEW_USING_CHOC && !(defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS))
  29. # undef WEB_VIEW_USING_CHOC
  30. # define WEB_VIEW_USING_CHOC 0
  31. #endif
  32. #if defined(DISTRHO_OS_MAC) && !WEB_VIEW_USING_CHOC
  33. # undef WEB_VIEW_USING_MACOS_WEBKIT
  34. # define WEB_VIEW_USING_MACOS_WEBKIT 1
  35. #else
  36. # undef WEB_VIEW_USING_MACOS_WEBKIT
  37. # define WEB_VIEW_USING_MACOS_WEBKIT 0
  38. #endif
  39. #if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX)
  40. # undef WEB_VIEW_USING_X11_IPC
  41. # define WEB_VIEW_USING_X11_IPC 1
  42. #else
  43. # undef WEB_VIEW_USING_X11_IPC
  44. # define WEB_VIEW_USING_X11_IPC 0
  45. #endif
  46. #if WEB_VIEW_USING_CHOC
  47. # define WC_ERR_INVALID_CHARS 0
  48. # include "../CHOC/gui/choc_WebView.h"
  49. #elif WEB_VIEW_USING_MACOS_WEBKIT
  50. # include <Cocoa/Cocoa.h>
  51. # include <WebKit/WebKit.h>
  52. #elif WEB_VIEW_USING_X11_IPC
  53. // #define QT_NO_VERSION_TAGGING
  54. // #include <QtCore/QChar>
  55. // #include <QtCore/QPoint>
  56. // #include <QtCore/QSize>
  57. // #undef signals
  58. # include "ChildProcess.hpp"
  59. # include "RingBuffer.hpp"
  60. # include "String.hpp"
  61. # include <clocale>
  62. # include <cstdio>
  63. # include <functional>
  64. # include <dlfcn.h>
  65. # include <fcntl.h>
  66. # include <pthread.h>
  67. # include <unistd.h>
  68. # include <sys/mman.h>
  69. # include <X11/Xlib.h>
  70. # ifdef __linux__
  71. # include <syscall.h>
  72. # include <linux/futex.h>
  73. # include <linux/limits.h>
  74. # else
  75. # include <semaphore.h>
  76. # endif
  77. #endif
  78. // -----------------------------------------------------------------------------------------------------------
  79. #if WEB_VIEW_USING_MACOS_WEBKIT
  80. #define MACRO_NAME2(a, b, c) a ## b ## c
  81. #define MACRO_NAME(a, b, c) MACRO_NAME2(a, b, c)
  82. #define WEB_VIEW_DELEGATE_CLASS_NAME \
  83. MACRO_NAME(WebViewDelegate_, _, DISTRHO_NAMESPACE)
  84. @interface WEB_VIEW_DELEGATE_CLASS_NAME : NSObject<WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate>
  85. @end
  86. @implementation WEB_VIEW_DELEGATE_CLASS_NAME {
  87. @public
  88. WebViewMessageCallback callback;
  89. void* callbackPtr;
  90. bool loaded;
  91. }
  92. - (void)webView:(WKWebView *)webview
  93. didFinishNavigation:(WKNavigation*)navigation
  94. {
  95. d_stdout("page loaded");
  96. loaded = true;
  97. }
  98. - (void)webView:(WKWebView*)webview
  99. runJavaScriptAlertPanelWithMessage:(NSString*)message
  100. initiatedByFrame:(WKFrameInfo*)frame
  101. completionHandler:(void (^)(void))completionHandler
  102. {
  103. NSAlert* const alert = [[NSAlert alloc] init];
  104. [alert addButtonWithTitle:@"OK"];
  105. [alert setInformativeText:message];
  106. [alert setMessageText:@"Alert"];
  107. dispatch_async(dispatch_get_main_queue(), ^
  108. {
  109. [alert beginSheetModalForWindow:[webview window]
  110. completionHandler:^(NSModalResponse)
  111. {
  112. completionHandler();
  113. [alert release];
  114. }];
  115. });
  116. }
  117. - (void)webView:(WKWebView*)webview
  118. runJavaScriptConfirmPanelWithMessage:(NSString*)message
  119. initiatedByFrame:(WKFrameInfo*)frame
  120. completionHandler:(void (^)(BOOL))completionHandler
  121. {
  122. NSAlert* const alert = [[NSAlert alloc] init];
  123. [alert addButtonWithTitle:@"OK"];
  124. [alert addButtonWithTitle:@"Cancel"];
  125. [alert setInformativeText:message];
  126. [alert setMessageText:@"Confirm"];
  127. dispatch_async(dispatch_get_main_queue(), ^
  128. {
  129. [alert beginSheetModalForWindow:[webview window]
  130. completionHandler:^(NSModalResponse result)
  131. {
  132. completionHandler(result == NSAlertFirstButtonReturn);
  133. [alert release];
  134. }];
  135. });
  136. }
  137. - (void)webView:(WKWebView*)webview
  138. runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
  139. defaultText:(NSString*)defaultText
  140. initiatedByFrame:(WKFrameInfo*)frame
  141. completionHandler:(void (^)(NSString*))completionHandler
  142. {
  143. NSTextField* const input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 250, 30)];
  144. [input setStringValue:defaultText];
  145. NSAlert* const alert = [[NSAlert alloc] init];
  146. [alert setAccessoryView:input];
  147. [alert addButtonWithTitle:@"OK"];
  148. [alert addButtonWithTitle:@"Cancel"];
  149. [alert setInformativeText:prompt];
  150. [alert setMessageText: @"Prompt"];
  151. dispatch_async(dispatch_get_main_queue(), ^
  152. {
  153. [alert beginSheetModalForWindow:[webview window]
  154. completionHandler:^(NSModalResponse result)
  155. {
  156. [input validateEditing];
  157. completionHandler(result == NSAlertFirstButtonReturn ? [input stringValue] : nil);
  158. [alert release];
  159. }];
  160. });
  161. }
  162. - (void)webView:(WKWebView*)webview
  163. runOpenPanelWithParameters:(WKOpenPanelParameters*)params
  164. initiatedByFrame:(WKFrameInfo*)frame
  165. completionHandler:(void (^)(NSArray<NSURL*>*))completionHandler
  166. {
  167. NSOpenPanel* const panel = [[NSOpenPanel alloc] init];
  168. [panel setAllowsMultipleSelection:[params allowsMultipleSelection]];
  169. // [panel setAllowedFileTypes:(NSArray<NSString*>*)[params _allowedFileExtensions]];
  170. [panel setCanChooseDirectories:[params allowsDirectories]];
  171. [panel setCanChooseFiles:![params allowsDirectories]];
  172. dispatch_async(dispatch_get_main_queue(), ^
  173. {
  174. [panel beginSheetModalForWindow:[webview window]
  175. completionHandler:^(NSModalResponse result)
  176. {
  177. completionHandler(result == NSModalResponseOK ? [panel URLs] : nil);
  178. [panel release];
  179. }];
  180. });
  181. }
  182. - (void)userContentController:(WKUserContentController*)userContentController
  183. didReceiveScriptMessage:(WKScriptMessage*)message
  184. {
  185. NSString* const nsstring = static_cast<NSString*>([message body]);
  186. char* const string = strdup([nsstring UTF8String]);
  187. d_debug("JS call received '%s' %p", string, callback);
  188. if (callback != nullptr)
  189. callback(callbackPtr, string);
  190. std::free(string);
  191. }
  192. @end
  193. #elif WEB_VIEW_USING_X11_IPC
  194. #endif // WEB_VIEW_USING_MACOS_WEBKIT
  195. // -----------------------------------------------------------------------------------------------------------
  196. #ifdef WEB_VIEW_DGL_NAMESPACE
  197. START_NAMESPACE_DGL
  198. using DISTRHO_NAMESPACE::String;
  199. #else
  200. START_NAMESPACE_DISTRHO
  201. #endif
  202. // -----------------------------------------------------------------------------------------------------------
  203. #if WEB_VIEW_USING_X11_IPC
  204. #ifdef __linux__
  205. typedef int32_t ipc_sem_t;
  206. #else
  207. typedef sem_t ipc_sem_t;
  208. #endif
  209. enum WebViewMessageType {
  210. kWebViewMessageNull,
  211. kWebViewMessageInitData,
  212. kWebViewMessageEvaluateJS,
  213. kWebViewMessageCallback,
  214. kWebViewMessageReload
  215. };
  216. struct WebViewSharedBuffer {
  217. static constexpr const uint32_t size = 0x100000;
  218. ipc_sem_t sem;
  219. uint32_t head, tail, wrtn;
  220. bool invalidateCommit;
  221. uint8_t buf[size];
  222. };
  223. struct WebViewRingBuffer {
  224. WebViewSharedBuffer server;
  225. WebViewSharedBuffer client;
  226. bool valid;
  227. };
  228. static void webview_wake(ipc_sem_t* const sem)
  229. {
  230. #ifdef __linux__
  231. if (__sync_bool_compare_and_swap(sem, 0, 1))
  232. syscall(SYS_futex, sem, FUTEX_WAKE, 1, nullptr, nullptr, 0);
  233. #else
  234. sem_post(sem);
  235. #endif
  236. }
  237. static bool webview_timedwait(ipc_sem_t* const sem)
  238. {
  239. #ifdef __linux__
  240. const struct timespec timeout = { 1, 0 };
  241. for (;;)
  242. {
  243. if (__sync_bool_compare_and_swap(sem, 1, 0))
  244. return true;
  245. if (syscall(SYS_futex, sem, FUTEX_WAIT, 0, &timeout, nullptr, 0) != 0)
  246. if (errno != EAGAIN && errno != EINTR)
  247. return false;
  248. }
  249. #else
  250. struct timespec timeout;
  251. if (clock_gettime(CLOCK_REALTIME, &timeout) != 0)
  252. return false;
  253. timeout.tv_sec += 1;
  254. for (int r;;)
  255. {
  256. r = sem_timedwait(sem, &timeout);
  257. if (r < 0)
  258. r = errno;
  259. if (r == EINTR)
  260. continue;
  261. return r == 0;
  262. }
  263. #endif
  264. }
  265. #endif
  266. struct WebViewData {
  267. #if WEB_VIEW_USING_CHOC
  268. choc::ui::WebView* const webview;
  269. #elif WEB_VIEW_USING_MACOS_WEBKIT
  270. NSView* view;
  271. WKWebView* webview;
  272. NSURLRequest* urlreq;
  273. WEB_VIEW_DELEGATE_CLASS_NAME* delegate;
  274. #elif WEB_VIEW_USING_X11_IPC
  275. int shmfd = 0;
  276. char shmname[128] = {};
  277. WebViewRingBuffer* shmptr = nullptr;
  278. WebViewMessageCallback callback = nullptr;
  279. void* callbackPtr = nullptr;
  280. ChildProcess p;
  281. RingBufferControl<WebViewSharedBuffer> rbctrl, rbctrl2;
  282. ::Display* display = nullptr;
  283. ::Window childWindow = 0;
  284. ::Window ourWindow = 0;
  285. #endif
  286. WebViewData() {}
  287. DISTRHO_DECLARE_NON_COPYABLE(WebViewData);
  288. };
  289. // -----------------------------------------------------------------------------------------------------------
  290. #if WEB_VIEW_USING_CHOC
  291. static std::optional<choc::ui::WebView::Options::Resource> fetch_resource(const std::string& path)
  292. {
  293. d_stdout("requested path %s", path.c_str());
  294. if (path == "/")
  295. {
  296. const std::string html = R"PREFIX(
  297. <html>
  298. <head>
  299. <style>
  300. html, body { background: black; background-image: url(img.svg); }
  301. </style>
  302. <script>
  303. function parameterChanged(index, value) {
  304. console.log("parameterChanged received", index, value);
  305. }
  306. </script>
  307. </head>
  308. <body>
  309. hello world!
  310. </body>
  311. </html>
  312. )PREFIX";
  313. const std::vector<uint8_t> data(html.begin(), html.end());
  314. return choc::ui::WebView::Options::Resource{ data, "text/html" };
  315. }
  316. if (path == "/img.svg")
  317. {
  318. const std::string html = R"PREFIX(<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  319. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  320. <!-- based on https://github.com/n0jo/rackwindows/blob/master/res/components/rw_knob_large_dark.svg -->
  321. <svg width="47px" height="47px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
  322. <g id="knobLDark">
  323. <path id="path3832" d="M23.521,45.109c-7.674,0 -3.302,3.9 -10.224,0.498c-6.922,-3.403 -1.202,-2.341 -5.997,-8.501c-4.795,-6.159 -5.059,-0.201 -6.763,-7.827c-1.704,-7.625 1.043,-2.42 2.76,-10.046c1.718,-7.626 -2.998,-4.102 1.797,-10.221c4.795,-6.12 2.51,-0.673 9.432,-4.035c6.921,-3.363 1.321,-4.977 8.995,-4.977c7.675,0 2.087,1.574 8.996,4.977c6.909,3.402 4.636,-2.045 9.432,4.035c4.795,6.078 0.079,2.689 1.796,10.26c1.717,7.572 4.465,2.422 2.761,10.048c-1.704,7.625 -1.982,1.708 -6.763,7.827c-4.782,6.119 0.924,5.057 -5.998,8.46c-6.921,3.402 -2.549,-0.498 -10.224,-0.498Z" style="fill:#ccc;fill-rule:nonzero;"/>
  324. </g>
  325. </svg>
  326. )PREFIX";
  327. const std::vector<uint8_t> data(html.begin(), html.end());
  328. return choc::ui::WebView::Options::Resource{ data, "image/svg+xml" };
  329. }
  330. return {};
  331. }
  332. #elif WEB_VIEW_USING_X11_IPC
  333. static void getFilenameFromFunctionPtr(char filename[PATH_MAX], const void* const ptr)
  334. {
  335. Dl_info info = {};
  336. dladdr(ptr, &info);
  337. if (info.dli_fname[0] == '.')
  338. {
  339. getcwd(filename, PATH_MAX - 1);
  340. std::strncat(filename, info.dli_fname + 1, PATH_MAX - 1);
  341. }
  342. else if (info.dli_fname[0] != '/')
  343. {
  344. getcwd(filename, PATH_MAX - 1);
  345. std::strncat(filename, "/", PATH_MAX - 1);
  346. std::strncat(filename, info.dli_fname, PATH_MAX - 1);
  347. }
  348. else
  349. {
  350. std::strncpy(filename, info.dli_fname, PATH_MAX - 1);
  351. }
  352. }
  353. #endif
  354. // -----------------------------------------------------------------------------------------------------------
  355. WebViewHandle webViewCreate(const char* const url,
  356. const uintptr_t windowId,
  357. const uint initialWidth,
  358. const uint initialHeight,
  359. const double scaleFactor,
  360. const WebViewOptions& options)
  361. {
  362. #if WEB_VIEW_USING_CHOC
  363. choc::ui::WebView::Options woptions;
  364. woptions.acceptsFirstMouseClick = true;
  365. woptions.enableDebugMode = true;
  366. woptions.fetchResource = fetch_resource;
  367. std::unique_ptr<choc::ui::WebView> webview = std::make_unique<choc::ui::WebView>(woptions);
  368. DISTRHO_SAFE_ASSERT_RETURN(webview->loadedOK(), nullptr);
  369. void* const handle = webview->getViewHandle();
  370. DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, nullptr);
  371. choc::ui::WebView* const www = webview.get();
  372. webview->bind("setParameterValue", [www](const choc::value::ValueView&) -> choc::value::Value {
  373. static int pp = 0;
  374. std::string toeval = "typeof(parameterChanged) === 'function' && parameterChanged(";
  375. toeval += std::to_string(++pp);
  376. toeval += ", 0.1)";
  377. d_stdout("param received | %s", toeval.c_str());
  378. www->evaluateJavascript(toeval);
  379. return {};
  380. });
  381. #ifdef DISTRHO_OS_MAC
  382. NSView* const view = static_cast<NSView*>(handle);
  383. [reinterpret_cast<NSView*>(windowId) addSubview:view];
  384. [view setFrame:NSMakeRect(options.offset.x,
  385. options.offset.y,
  386. DISTRHO_UI_DEFAULT_WIDTH - options.offset.x,
  387. DISTRHO_UI_DEFAULT_HEIGHT - options.offset.y)];
  388. #else
  389. const HWND hwnd = static_cast<HWND>(handle);
  390. LONG_PTR flags = GetWindowLongPtr(hwnd, -16);
  391. flags = (flags & ~WS_POPUP) | WS_CHILD;
  392. SetWindowLongPtr(hwnd, -16, flags);
  393. SetParent(hwnd, reinterpret_cast<HWND>(windowId));
  394. SetWindowPos(hwnd, nullptr,
  395. options.offset.x * scaleFactor,
  396. options.offset.y * scaleFactor,
  397. (initialWidth - options.offset.x) * scaleFactor,
  398. (initialHeight - options.offset.y) * scaleFactor,
  399. SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  400. ShowWindow(hwnd, SW_SHOW);
  401. #endif
  402. return new WebViewData{options.callback, webview.release()};
  403. #elif WEB_VIEW_USING_MACOS_WEBKIT
  404. NSView* const view = reinterpret_cast<NSView*>(windowId);
  405. WKPreferences* const prefs = [[WKPreferences alloc] init];
  406. [prefs setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
  407. [prefs setValue:@YES forKey:@"DOMPasteAllowed"];
  408. // if (debug)
  409. {
  410. [prefs setValue:@YES forKey:@"developerExtrasEnabled"];
  411. // TODO enable_write_console_messages_to_stdout
  412. }
  413. WKWebViewConfiguration* const config = [[WKWebViewConfiguration alloc] init];
  414. config.limitsNavigationsToAppBoundDomains = false;
  415. config.preferences = prefs;
  416. const CGRect rect = CGRectMake(options.offset.x / scaleFactor,
  417. options.offset.y / scaleFactor,
  418. initialWidth,
  419. initialHeight);
  420. WKWebView* const webview = [[WKWebView alloc] initWithFrame:rect
  421. configuration:config];
  422. [webview setHidden:YES];
  423. [view addSubview:webview];
  424. // TODO webkit_web_view_set_background_color
  425. WEB_VIEW_DELEGATE_CLASS_NAME* const delegate = [[WEB_VIEW_DELEGATE_CLASS_NAME alloc] init];
  426. delegate->callback = options.callback;
  427. delegate->callbackPtr = options.callbackPtr;
  428. delegate->loaded = false;
  429. if (WKUserContentController* const controller = [config userContentController])
  430. {
  431. [controller retain];
  432. [controller addScriptMessageHandler:delegate name:@"external"];
  433. if (options.initialJS != nullptr)
  434. {
  435. NSString* const nsInitialJS = [[NSString alloc] initWithBytes:options.initialJS
  436. length:std::strlen(options.initialJS)
  437. encoding:NSUTF8StringEncoding];
  438. WKUserScript* const script = [[WKUserScript alloc] initWithSource:nsInitialJS
  439. injectionTime:WKUserScriptInjectionTimeAtDocumentStart
  440. forMainFrameOnly:true];
  441. [controller addUserScript:script];
  442. [script release];
  443. [nsInitialJS release];
  444. }
  445. }
  446. [webview setNavigationDelegate:delegate];
  447. [webview setUIDelegate:delegate];
  448. NSString* const nsurl = [[NSString alloc] initWithBytes:url
  449. length:std::strlen(url)
  450. encoding:NSUTF8StringEncoding];
  451. NSURLRequest* const urlreq = [[NSURLRequest alloc] initWithURL: [NSURL URLWithString: nsurl]];
  452. d_stdout("url is '%s'", url);
  453. if (std::strncmp(url, "file://", 7) == 0)
  454. {
  455. const char* const lastsep = std::strrchr(url + 7, '/');
  456. NSString* const urlpath = [[NSString alloc] initWithBytes:url
  457. length:(lastsep - url)
  458. encoding:NSUTF8StringEncoding];
  459. [webview loadFileRequest:urlreq
  460. allowingReadAccessToURL:[NSURL URLWithString:urlpath]];
  461. [urlpath release];
  462. }
  463. else
  464. {
  465. [webview loadRequest:urlreq];
  466. }
  467. d_stdout("waiting for load");
  468. if (! delegate->loaded)
  469. {
  470. NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init];
  471. NSDate* const date = [NSDate dateWithTimeIntervalSinceNow:0.05];
  472. NSEvent* event;
  473. while (! delegate->loaded)
  474. {
  475. event = [NSApp
  476. #ifdef __MAC_10_12
  477. nextEventMatchingMask:NSEventMaskAny
  478. #else
  479. nextEventMatchingMask:NSAnyEventMask
  480. #endif
  481. untilDate:date
  482. inMode:NSDefaultRunLoopMode
  483. dequeue:YES];
  484. if (event == nil)
  485. break;
  486. [NSApp sendEvent: event];
  487. }
  488. [pool release];
  489. }
  490. d_stdout("waiting done");
  491. [webview setHidden:NO];
  492. [nsurl release];
  493. [config release];
  494. [prefs release];
  495. WebViewData* const handle = new WebViewData;
  496. handle->view = view;
  497. handle->webview = webview;
  498. handle->urlreq = urlreq;
  499. handle->delegate = delegate;
  500. return handle;
  501. #elif WEB_VIEW_USING_X11_IPC
  502. // get startup paths
  503. char ldlinux[PATH_MAX] = {};
  504. getFilenameFromFunctionPtr(ldlinux, dlsym(nullptr, "_rtld_global"));
  505. char filename[PATH_MAX] = {};
  506. getFilenameFromFunctionPtr(filename, reinterpret_cast<const void*>(webViewCreate));
  507. d_stdout("ld-linux is '%s'", ldlinux);
  508. d_stdout("filename is '%s'", filename);
  509. // setup shared memory
  510. int shmfd;
  511. char shmname[128];
  512. void* shmptr;
  513. for (int i = 0; i < 9999; ++i)
  514. {
  515. snprintf(shmname, sizeof(shmname) - 1, "/dpf-webview-%d", i + 1);
  516. shmfd = shm_open(shmname, O_CREAT|O_EXCL|O_RDWR, 0666);
  517. if (shmfd < 0)
  518. continue;
  519. if (ftruncate(shmfd, sizeof(WebViewRingBuffer)) != 0)
  520. {
  521. close(shmfd);
  522. shm_unlink(shmname);
  523. continue;
  524. }
  525. break;
  526. }
  527. if (shmfd < 0)
  528. {
  529. d_stderr("shm_open failed: %s", strerror(errno));
  530. return nullptr;
  531. }
  532. shmptr = mmap(nullptr, sizeof(WebViewRingBuffer), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
  533. if (shmptr == nullptr || shmptr == MAP_FAILED)
  534. {
  535. d_stderr("mmap failed: %s", strerror(errno));
  536. close(shmfd);
  537. shm_unlink(shmname);
  538. return nullptr;
  539. }
  540. #ifndef __linux__
  541. sem_init(&handle->shmptr->client.sem, 1, 0);
  542. sem_init(&handle->shmptr->server.sem, 1, 0);
  543. #endif
  544. ::Display* const display = XOpenDisplay(nullptr);
  545. DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, nullptr);
  546. // set up custom child environment
  547. uint envsize = 0;
  548. while (environ[envsize] != nullptr)
  549. ++envsize;
  550. char** const envp = new char*[envsize + 5];
  551. {
  552. uint e = 0;
  553. for (uint i = 0; i < envsize; ++i)
  554. {
  555. if (std::strncmp(environ[i], "LD_PRELOAD=", 11) == 0)
  556. continue;
  557. if (std::strncmp(environ[i], "LD_LIBRARY_PATH=", 16) == 0)
  558. continue;
  559. envp[e++] = strdup(environ[i]);
  560. }
  561. envp[e++] = strdup("LANG=en_US.UTF-8");
  562. envp[e++] = ("DPF_WEB_VIEW_SCALE_FACTOR=" + String(scaleFactor)).getAndReleaseBuffer();
  563. envp[e++] = ("DPF_WEB_VIEW_WIN_ID=" +String(windowId)).getAndReleaseBuffer();
  564. for (uint i = e; i < envsize + 5; ++i)
  565. envp[e++] = nullptr;
  566. }
  567. WebViewData* const handle = new WebViewData;
  568. handle->callback = options.callback;
  569. handle->callbackPtr = options.callbackPtr;
  570. handle->shmfd = shmfd;
  571. handle->shmptr = static_cast<WebViewRingBuffer*>(shmptr);
  572. handle->display = display;
  573. handle->ourWindow = windowId;
  574. std::memcpy(handle->shmname, shmname, sizeof(shmname));
  575. handle->shmptr->valid = true;
  576. handle->rbctrl.setRingBuffer(&handle->shmptr->client, false);
  577. handle->rbctrl.flush();
  578. handle->rbctrl2.setRingBuffer(&handle->shmptr->server, false);
  579. handle->rbctrl2.flush();
  580. const char* const args[] = { ldlinux, filename, "dpf-ld-linux-webview", shmname, nullptr };
  581. handle->p.start(args, envp);
  582. for (uint i = 0; envp[i] != nullptr; ++i)
  583. std::free(envp[i]);
  584. delete[] envp;
  585. const size_t urllen = std::strlen(url);
  586. const size_t initjslen = options.initialJS != nullptr ? std::strlen(options.initialJS) + 1 : 0;
  587. handle->rbctrl.writeUInt(kWebViewMessageInitData) &&
  588. handle->rbctrl.writeULong(windowId) &&
  589. handle->rbctrl.writeUInt(initialWidth) &&
  590. handle->rbctrl.writeUInt(initialHeight) &&
  591. handle->rbctrl.writeDouble(scaleFactor) &&
  592. handle->rbctrl.writeInt(options.offset.x) &&
  593. handle->rbctrl.writeInt(options.offset.y) &&
  594. handle->rbctrl.writeUInt(urllen) &&
  595. handle->rbctrl.writeCustomData(url, urllen) &&
  596. handle->rbctrl.writeUInt(initjslen) &&
  597. initjslen != 0 &&
  598. handle->rbctrl.writeCustomData(options.initialJS, initjslen);
  599. handle->rbctrl.commitWrite();
  600. webview_wake(&handle->shmptr->client.sem);
  601. for (int i = 0; i < 5 && handle->p.isRunning(); ++i)
  602. {
  603. if (webview_timedwait(&handle->shmptr->server.sem))
  604. return handle;
  605. }
  606. d_stderr("webview client side failed to start");
  607. webViewDestroy(handle);
  608. return nullptr;
  609. #endif
  610. // maybe unused
  611. (void)windowId;
  612. (void)initialWidth;
  613. (void)initialHeight;
  614. (void)scaleFactor;
  615. (void)options;
  616. return nullptr;
  617. }
  618. void webViewDestroy(const WebViewHandle handle)
  619. {
  620. #if WEB_VIEW_USING_CHOC
  621. delete handle->webview;
  622. #elif WEB_VIEW_USING_MACOS_WEBKIT
  623. [handle->webview setHidden:YES];
  624. [handle->webview removeFromSuperview];
  625. [handle->urlreq release];
  626. // [handle->delegate release];
  627. #elif WEB_VIEW_USING_X11_IPC
  628. munmap(handle->shmptr, sizeof(WebViewRingBuffer));
  629. close(handle->shmfd);
  630. shm_unlink(handle->shmname);
  631. XCloseDisplay(handle->display);
  632. #endif
  633. delete handle;
  634. }
  635. void webViewIdle(const WebViewHandle handle)
  636. {
  637. #if WEB_VIEW_USING_X11_IPC
  638. uint32_t size = 0;
  639. void* buffer = nullptr;
  640. while (handle->rbctrl2.isDataAvailableForReading())
  641. {
  642. switch (handle->rbctrl2.readUInt())
  643. {
  644. case kWebViewMessageCallback:
  645. if (const uint32_t len = handle->rbctrl2.readUInt())
  646. {
  647. if (len > size)
  648. {
  649. size = len;
  650. buffer = std::realloc(buffer, len);
  651. if (buffer == nullptr)
  652. {
  653. d_stderr("server out of memory, abort!");
  654. handle->rbctrl2.flush();
  655. return;
  656. }
  657. }
  658. if (handle->rbctrl2.readCustomData(buffer, len))
  659. {
  660. d_debug("server kWebViewMessageCallback -> '%s'", static_cast<char*>(buffer));
  661. if (handle->callback != nullptr)
  662. handle->callback(handle->callbackPtr, static_cast<char*>(buffer));
  663. continue;
  664. }
  665. }
  666. break;
  667. }
  668. d_stderr("server ringbuffer data race, abort!");
  669. handle->rbctrl2.flush();
  670. return;
  671. }
  672. #else
  673. // unused
  674. (void)handle;
  675. #endif
  676. }
  677. void webViewEvaluateJS(const WebViewHandle handle, const char* const js)
  678. {
  679. #if WEB_VIEW_USING_CHOC
  680. #elif WEB_VIEW_USING_MACOS_WEBKIT
  681. NSString* const nsjs = [[NSString alloc] initWithBytes:js
  682. length:std::strlen(js)
  683. encoding:NSUTF8StringEncoding];
  684. [handle->webview evaluateJavaScript:nsjs completionHandler:nil];
  685. [nsjs release];
  686. #elif WEB_VIEW_USING_X11_IPC
  687. d_debug("evaluateJS '%s'", js);
  688. const size_t len = std::strlen(js) + 1;
  689. handle->rbctrl.writeUInt(kWebViewMessageEvaluateJS) &&
  690. handle->rbctrl.writeUInt(len) &&
  691. handle->rbctrl.writeCustomData(js, len);
  692. if (handle->rbctrl.commitWrite())
  693. webview_wake(&handle->shmptr->client.sem);
  694. #endif
  695. // maybe unused
  696. (void)handle;
  697. (void)js;
  698. }
  699. void webViewReload(const WebViewHandle handle)
  700. {
  701. #if WEB_VIEW_USING_CHOC
  702. #elif WEB_VIEW_USING_MACOS_WEBKIT
  703. [handle->webview loadRequest:handle->urlreq];
  704. #elif WEB_VIEW_USING_X11_IPC
  705. d_stdout("reload");
  706. handle->rbctrl.writeUInt(kWebViewMessageReload);
  707. if (handle->rbctrl.commitWrite())
  708. webview_wake(&handle->shmptr->client.sem);
  709. #endif
  710. // maybe unused
  711. (void)handle;
  712. }
  713. void webViewResize(const WebViewHandle handle, const uint width, const uint height, const double scaleFactor)
  714. {
  715. #if WEB_VIEW_USING_CHOC
  716. #ifdef DISTRHO_OS_MAC
  717. NSView* const view = static_cast<NSView*>(handle->webview->getViewHandle());
  718. [view setFrameSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
  719. #else
  720. const HWND hwnd = static_cast<HWND>(handle->webview->getViewHandle());
  721. SetWindowPos(hwnd, nullptr, 0, 0,
  722. width, height,
  723. SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
  724. #endif
  725. #elif WEB_VIEW_USING_MACOS_WEBKIT
  726. [handle->webview setFrameSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
  727. #elif WEB_VIEW_USING_X11_IPC
  728. if (handle->childWindow == 0)
  729. {
  730. ::Window rootWindow, parentWindow;
  731. ::Window* childWindows = nullptr;
  732. uint numChildren = 0;
  733. XFlush(handle->display);
  734. XQueryTree(handle->display, handle->ourWindow, &rootWindow, &parentWindow, &childWindows, &numChildren);
  735. if (numChildren == 0 || childWindows == nullptr)
  736. return;
  737. handle->childWindow = childWindows[0];
  738. XFree(childWindows);
  739. }
  740. XResizeWindow(handle->display, handle->childWindow, width, height);
  741. XFlush(handle->display);
  742. #endif
  743. // maybe unused
  744. (void)handle;
  745. (void)width;
  746. (void)height;
  747. (void)scaleFactor;
  748. }
  749. #if WEB_VIEW_USING_X11_IPC
  750. // -----------------------------------------------------------------------------------------------------------
  751. static std::function<void(const char* js)> evaluateFn;
  752. static std::function<void()> reloadFn;
  753. static std::function<void()> terminateFn;
  754. static std::function<void(WebViewRingBuffer* rb)> wakeFn;
  755. // -----------------------------------------------------------------------------------------------------------
  756. struct GtkContainer;
  757. struct GtkPlug;
  758. struct GtkWidget;
  759. struct GtkWindow;
  760. struct JSCValue;
  761. struct WebKitJavascriptResult;
  762. struct WebKitSettings;
  763. struct WebKitUserContentManager;
  764. struct WebKitUserScript;
  765. struct WebKitWebView;
  766. typedef int gboolean;
  767. #define G_CALLBACK(p) reinterpret_cast<void*>(p)
  768. #define GTK_CONTAINER(p) reinterpret_cast<GtkContainer*>(p)
  769. #define GTK_PLUG(p) reinterpret_cast<GtkPlug*>(p)
  770. #define GTK_WINDOW(p) reinterpret_cast<GtkWindow*>(p)
  771. #define WEBKIT_WEB_VIEW(p) reinterpret_cast<WebKitWebView*>(p)
  772. // struct QApplication;
  773. // struct QUrl;
  774. // struct QWebEngineView;
  775. // struct QWindow;
  776. // -----------------------------------------------------------------------------------------------------------
  777. #define JOIN(A, B) A ## B
  778. #define AUTOSYM(S) \
  779. using JOIN(gtk3_, S) = decltype(&S); \
  780. JOIN(gtk3_, S) S = reinterpret_cast<JOIN(gtk3_, S)>(dlsym(nullptr, #S)); \
  781. DISTRHO_SAFE_ASSERT_RETURN(S != nullptr, false);
  782. #define CSYM(S, NAME) \
  783. S NAME = reinterpret_cast<S>(dlsym(nullptr, #NAME)); \
  784. DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false);
  785. #define CPPSYM(S, NAME, SN) \
  786. S NAME = reinterpret_cast<S>(dlsym(nullptr, #SN)); \
  787. DISTRHO_SAFE_ASSERT_RETURN(NAME != nullptr, false);
  788. // -----------------------------------------------------------------------------------------------------------
  789. // gtk3 variant
  790. static void gtk3_idle(void* const ptr)
  791. {
  792. WebViewRingBuffer* const shmptr = static_cast<WebViewRingBuffer*>(ptr);
  793. RingBufferControl<WebViewSharedBuffer> rbctrl;
  794. rbctrl.setRingBuffer(&shmptr->client, false);
  795. uint32_t size = 0;
  796. void* buffer = nullptr;
  797. while (rbctrl.isDataAvailableForReading())
  798. {
  799. switch (rbctrl.readUInt())
  800. {
  801. case kWebViewMessageEvaluateJS:
  802. if (const uint32_t len = rbctrl.readUInt())
  803. {
  804. if (len > size)
  805. {
  806. size = len;
  807. buffer = realloc(buffer, len);
  808. if (buffer == nullptr)
  809. {
  810. d_stderr("lv2ui client out of memory, abort!");
  811. abort();
  812. }
  813. }
  814. if (rbctrl.readCustomData(buffer, len))
  815. {
  816. d_debug("client kWebViewMessageEvaluateJS -> '%s'", static_cast<char*>(buffer));
  817. evaluateFn(static_cast<char*>(buffer));
  818. continue;
  819. }
  820. }
  821. break;
  822. case kWebViewMessageReload:
  823. d_debug("client kWebViewMessageReload");
  824. reloadFn();
  825. continue;
  826. }
  827. d_stderr("client ringbuffer data race, abort!");
  828. abort();
  829. }
  830. free(buffer);
  831. }
  832. static int gtk3_js_cb(WebKitUserContentManager*, WebKitJavascriptResult* const result, void* const arg)
  833. {
  834. WebViewRingBuffer* const shmptr = static_cast<WebViewRingBuffer*>(arg);
  835. using g_free_t = void (*)(void*);
  836. using jsc_value_to_string_t = char* (*)(JSCValue*);
  837. using webkit_javascript_result_get_js_value_t = JSCValue* (*)(WebKitJavascriptResult*);
  838. CSYM(g_free_t, g_free)
  839. CSYM(jsc_value_to_string_t, jsc_value_to_string)
  840. CSYM(webkit_javascript_result_get_js_value_t, webkit_javascript_result_get_js_value)
  841. JSCValue* const value = webkit_javascript_result_get_js_value(result);
  842. DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, false);
  843. char* const string = jsc_value_to_string(value);
  844. DISTRHO_SAFE_ASSERT_RETURN(string != nullptr, false);
  845. d_debug("js call received with data '%s'", string);
  846. const size_t len = std::strlen(string);
  847. RingBufferControl<WebViewSharedBuffer> rbctrl2;
  848. rbctrl2.setRingBuffer(&shmptr->server, false);
  849. rbctrl2.writeUInt(kWebViewMessageCallback) &&
  850. rbctrl2.writeUInt(len) &&
  851. rbctrl2.writeCustomData(string, len);
  852. rbctrl2.commitWrite();
  853. g_free(string);
  854. return 0;
  855. }
  856. static bool gtk3(Display* const display,
  857. const Window winId,
  858. const int x,
  859. const int y,
  860. const uint width,
  861. const uint height,
  862. double scaleFactor,
  863. const char* const url,
  864. const char* const initialJS,
  865. WebViewRingBuffer* const shmptr)
  866. {
  867. void* lib;
  868. if ((lib = dlopen("libwebkit2gtk-4.0.so.37", RTLD_NOW|RTLD_GLOBAL)) == nullptr ||
  869. (lib = dlopen("libwebkit2gtk-4.0.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr)
  870. return false;
  871. using g_main_context_invoke_t = void (*)(void*, void*, void*);
  872. using g_signal_connect_data_t = ulong (*)(void*, const char*, void*, void*, void*, int);
  873. using gdk_set_allowed_backends_t = void (*)(const char*);
  874. using gtk_container_add_t = void (*)(GtkContainer*, GtkWidget*);
  875. using gtk_init_check_t = gboolean (*)(int*, char***);
  876. using gtk_main_t = void (*)();
  877. using gtk_main_quit_t = void (*)();
  878. using gtk_plug_get_id_t = Window (*)(GtkPlug*);
  879. using gtk_plug_new_t = GtkWidget* (*)(Window);
  880. using gtk_widget_show_all_t = void (*)(GtkWidget*);
  881. using gtk_window_move_t = void (*)(GtkWindow*, int, int);
  882. using gtk_window_set_default_size_t = void (*)(GtkWindow*, int, int);
  883. using webkit_settings_new_t = WebKitSettings* (*)();
  884. using webkit_settings_set_enable_developer_extras_t = void (*)(WebKitSettings*, gboolean);
  885. using webkit_settings_set_enable_write_console_messages_to_stdout_t = void (*)(WebKitSettings*, gboolean);
  886. using webkit_settings_set_hardware_acceleration_policy_t = void (*)(WebKitSettings*, int);
  887. using webkit_settings_set_javascript_can_access_clipboard_t = void (*)(WebKitSettings*, gboolean);
  888. using webkit_user_content_manager_add_script_t = void (*)(WebKitUserContentManager*, WebKitUserScript*);
  889. using webkit_user_content_manager_register_script_message_handler_t = gboolean (*)(WebKitUserContentManager*, const char*);
  890. using webkit_user_script_new_t = WebKitUserScript* (*)(const char*, int, int, const char* const*, const char* const*);
  891. using webkit_web_view_evaluate_javascript_t = void* (*)(WebKitWebView*, const char*, ssize_t, const char*, const char*, void*, void*, void*);
  892. using webkit_web_view_get_user_content_manager_t = WebKitUserContentManager* (*)(WebKitWebView*);
  893. using webkit_web_view_load_uri_t = void (*)(WebKitWebView*, const char*);
  894. using webkit_web_view_new_with_settings_t = GtkWidget* (*)(WebKitSettings*);
  895. using webkit_web_view_run_javascript_t = void* (*)(WebKitWebView*, const char*, void*, void*, void*);
  896. using webkit_web_view_set_background_color_t = void (*)(WebKitWebView*, const double*);
  897. CSYM(g_main_context_invoke_t, g_main_context_invoke)
  898. CSYM(g_signal_connect_data_t, g_signal_connect_data)
  899. CSYM(gdk_set_allowed_backends_t, gdk_set_allowed_backends)
  900. CSYM(gtk_container_add_t, gtk_container_add)
  901. CSYM(gtk_init_check_t, gtk_init_check)
  902. CSYM(gtk_main_t, gtk_main)
  903. CSYM(gtk_main_quit_t, gtk_main_quit)
  904. CSYM(gtk_plug_get_id_t, gtk_plug_get_id)
  905. CSYM(gtk_plug_new_t, gtk_plug_new)
  906. CSYM(gtk_widget_show_all_t, gtk_widget_show_all)
  907. CSYM(gtk_window_move_t, gtk_window_move)
  908. CSYM(gtk_window_set_default_size_t, gtk_window_set_default_size)
  909. CSYM(webkit_settings_new_t, webkit_settings_new)
  910. CSYM(webkit_settings_set_enable_developer_extras_t, webkit_settings_set_enable_developer_extras)
  911. CSYM(webkit_settings_set_enable_write_console_messages_to_stdout_t, webkit_settings_set_enable_write_console_messages_to_stdout)
  912. CSYM(webkit_settings_set_hardware_acceleration_policy_t, webkit_settings_set_hardware_acceleration_policy)
  913. CSYM(webkit_settings_set_javascript_can_access_clipboard_t, webkit_settings_set_javascript_can_access_clipboard)
  914. CSYM(webkit_user_content_manager_add_script_t, webkit_user_content_manager_add_script)
  915. CSYM(webkit_user_content_manager_register_script_message_handler_t, webkit_user_content_manager_register_script_message_handler)
  916. CSYM(webkit_user_script_new_t, webkit_user_script_new)
  917. CSYM(webkit_web_view_get_user_content_manager_t, webkit_web_view_get_user_content_manager)
  918. CSYM(webkit_web_view_load_uri_t, webkit_web_view_load_uri)
  919. CSYM(webkit_web_view_new_with_settings_t, webkit_web_view_new_with_settings)
  920. CSYM(webkit_web_view_set_background_color_t, webkit_web_view_set_background_color)
  921. // special case for legacy API handling
  922. webkit_web_view_evaluate_javascript_t webkit_web_view_evaluate_javascript = reinterpret_cast<webkit_web_view_evaluate_javascript_t>(dlsym(nullptr, "webkit_web_view_evaluate_javascript"));
  923. webkit_web_view_run_javascript_t webkit_web_view_run_javascript = reinterpret_cast<webkit_web_view_run_javascript_t>(dlsym(nullptr, "webkit_web_view_run_javascript"));
  924. DISTRHO_SAFE_ASSERT_RETURN(webkit_web_view_evaluate_javascript != nullptr || webkit_web_view_run_javascript != nullptr, false);
  925. const int gdkScale = std::fmod(scaleFactor, 1.0) >= 0.75
  926. ? static_cast<int>(scaleFactor + 0.5)
  927. : static_cast<int>(scaleFactor);
  928. if (gdkScale != 1)
  929. {
  930. char scale[8] = {};
  931. std::snprintf(scale, 7, "%d", gdkScale);
  932. setenv("GDK_SCALE", scale, 1);
  933. std::snprintf(scale, 7, "%.2f", (1.0 / scaleFactor) * 1.2);
  934. setenv("GDK_DPI_SCALE", scale, 1);
  935. }
  936. else if (scaleFactor > 1.0)
  937. {
  938. char scale[8] = {};
  939. std::snprintf(scale, 7, "%.2f", (1.0 / scaleFactor) * 1.4);
  940. setenv("GDK_DPI_SCALE", scale, 1);
  941. }
  942. scaleFactor /= gdkScale;
  943. gdk_set_allowed_backends("x11");
  944. if (! gtk_init_check (nullptr, nullptr))
  945. return false;
  946. GtkWidget* const window = gtk_plug_new(winId);
  947. DISTRHO_SAFE_ASSERT_RETURN(window != nullptr, false);
  948. gtk_window_set_default_size(GTK_WINDOW(window),
  949. (width - x) * scaleFactor,
  950. (height - y) * scaleFactor);
  951. gtk_window_move(GTK_WINDOW(window), x * scaleFactor, y * scaleFactor);
  952. WebKitSettings* const settings = webkit_settings_new();
  953. DISTRHO_SAFE_ASSERT_RETURN(settings != nullptr, false);
  954. // TODO DOMPasteAllowed
  955. webkit_settings_set_javascript_can_access_clipboard(settings, true);
  956. webkit_settings_set_hardware_acceleration_policy(settings, 2 /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */);
  957. // if (debug)
  958. {
  959. webkit_settings_set_enable_developer_extras(settings, true);
  960. webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
  961. }
  962. GtkWidget* const webview = webkit_web_view_new_with_settings(settings);
  963. DISTRHO_SAFE_ASSERT_RETURN(webview != nullptr, false);
  964. const double color[] = {49.0/255, 54.0/255, 59.0/255, 1};
  965. webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), color);
  966. if (WebKitUserContentManager* const manager = webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview)))
  967. {
  968. if (initialJS != nullptr)
  969. {
  970. WebKitUserScript* const script = webkit_user_script_new(initialJS, 0, 0, nullptr, nullptr);
  971. webkit_user_content_manager_add_script(manager, script);
  972. }
  973. g_signal_connect_data(manager, "script-message-received::external", G_CALLBACK(gtk3_js_cb), shmptr, nullptr, 0);
  974. webkit_user_content_manager_register_script_message_handler(manager, "external");
  975. }
  976. webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
  977. gtk_container_add(GTK_CONTAINER(window), webview);
  978. gtk_widget_show_all(window);
  979. Window wid = gtk_plug_get_id(GTK_PLUG(window));
  980. XMapWindow(display, wid);
  981. XFlush(display);
  982. evaluateFn = [=](const char* const js){
  983. if (webkit_web_view_evaluate_javascript != nullptr)
  984. webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(webview), js, -1,
  985. nullptr, nullptr, nullptr, nullptr, nullptr);
  986. else
  987. webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(webview), js, nullptr, nullptr, nullptr);
  988. };
  989. reloadFn = [=](){
  990. webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url);
  991. };
  992. terminateFn = [=](){
  993. d_stdout("terminateFn");
  994. static bool quit = true;
  995. if (quit)
  996. {
  997. quit = false;
  998. gtk_main_quit();
  999. }
  1000. };
  1001. wakeFn = [=](WebViewRingBuffer* const rb){
  1002. g_main_context_invoke(NULL, G_CALLBACK(gtk3_idle), rb);
  1003. };
  1004. // notify server we started ok
  1005. webview_wake(&shmptr->server.sem);
  1006. gtk_main();
  1007. d_stdout("quit");
  1008. dlclose(lib);
  1009. return true;
  1010. }
  1011. #if 0
  1012. // -----------------------------------------------------------------------------------------------------------
  1013. // qt5webengine variant
  1014. static bool qt5webengine(const Window winId, const double scaleFactor, const char* const url)
  1015. {
  1016. void* lib;
  1017. if ((lib = dlopen("libQt5WebEngineWidgets.so.5", RTLD_NOW|RTLD_GLOBAL)) == nullptr ||
  1018. (lib = dlopen("libQt5WebEngineWidgets.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr)
  1019. return false;
  1020. using QApplication__init_t = void (*)(QApplication*, int&, char**, int);
  1021. using QApplication_exec_t = void (*)();
  1022. using QApplication_setAttribute_t = void (*)(Qt::ApplicationAttribute, bool);
  1023. using QString__init_t = void (*)(void*, const QChar*, ptrdiff_t);
  1024. using QUrl__init_t = void (*)(void*, const QString&, int /* QUrl::ParsingMode */);
  1025. using QWebEngineView__init_t = void (*)(QWebEngineView*, void*);
  1026. using QWebEngineView_move_t = void (*)(QWebEngineView*, const QPoint&);
  1027. using QWebEngineView_resize_t = void (*)(QWebEngineView*, const QSize&);
  1028. using QWebEngineView_setUrl_t = void (*)(QWebEngineView*, const QUrl&);
  1029. using QWebEngineView_show_t = void (*)(QWebEngineView*);
  1030. using QWebEngineView_winId_t = ulonglong (*)(QWebEngineView*);
  1031. using QWebEngineView_windowHandle_t = QWindow* (*)(QWebEngineView*);
  1032. using QWindow_fromWinId_t = QWindow* (*)(ulonglong);
  1033. using QWindow_setParent_t = void (*)(QWindow*, void*);
  1034. CPPSYM(QApplication__init_t, QApplication__init, _ZN12QApplicationC1ERiPPci)
  1035. CPPSYM(QApplication_exec_t, QApplication_exec, _ZN15QGuiApplication4execEv)
  1036. CPPSYM(QApplication_setAttribute_t, QApplication_setAttribute, _ZN16QCoreApplication12setAttributeEN2Qt20ApplicationAttributeEb)
  1037. CPPSYM(QString__init_t, QString__init, _ZN7QStringC2EPK5QChari)
  1038. CPPSYM(QUrl__init_t, QUrl__init, _ZN4QUrlC1ERK7QStringNS_11ParsingModeE)
  1039. CPPSYM(QWebEngineView__init_t, QWebEngineView__init, _ZN14QWebEngineViewC1EP7QWidget)
  1040. CPPSYM(QWebEngineView_move_t, QWebEngineView_move, _ZN7QWidget4moveERK6QPoint)
  1041. CPPSYM(QWebEngineView_resize_t, QWebEngineView_resize, _ZN7QWidget6resizeERK5QSize)
  1042. CPPSYM(QWebEngineView_setUrl_t, QWebEngineView_setUrl, _ZN14QWebEngineView6setUrlERK4QUrl)
  1043. CPPSYM(QWebEngineView_show_t, QWebEngineView_show, _ZN7QWidget4showEv)
  1044. CPPSYM(QWebEngineView_winId_t, QWebEngineView_winId, _ZNK7QWidget5winIdEv)
  1045. CPPSYM(QWebEngineView_windowHandle_t, QWebEngineView_windowHandle, _ZNK7QWidget12windowHandleEv)
  1046. CPPSYM(QWindow_fromWinId_t, QWindow_fromWinId, _ZN7QWindow9fromWinIdEy)
  1047. CPPSYM(QWindow_setParent_t, QWindow_setParent, _ZN7QWindow9setParentEPS_)
  1048. unsetenv("QT_FONT_DPI");
  1049. unsetenv("QT_SCREEN_SCALE_FACTORS");
  1050. unsetenv("QT_USE_PHYSICAL_DPI");
  1051. setenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0", 1);
  1052. char scale[8] = {};
  1053. std::snprintf(scale, 7, "%.2f", scaleFactor);
  1054. setenv("QT_SCALE_FACTOR", scale, 1);
  1055. QApplication_setAttribute(Qt::AA_X11InitThreads, true);
  1056. QApplication_setAttribute(Qt::AA_EnableHighDpiScaling, true);
  1057. QApplication_setAttribute(Qt::AA_UseHighDpiPixmaps, true);
  1058. static int argc = 0;
  1059. static char* argv[] = { nullptr };
  1060. uint8_t _app[64]; // sizeof(QApplication) == 16
  1061. QApplication* const app = reinterpret_cast<QApplication*>(_app);
  1062. QApplication__init(app, argc, argv, 0);
  1063. uint8_t _qstrurl[32]; // sizeof(QString) == 8
  1064. QString* const qstrurl(reinterpret_cast<QString*>(_qstrurl));
  1065. {
  1066. const size_t url_len = std::strlen(url);
  1067. QChar* const url_qchar = new QChar[url_len + 1];
  1068. for (size_t i = 0; i < url_len; ++i)
  1069. url_qchar[i] = QChar(url[i]);
  1070. url_qchar[url_len] = 0;
  1071. QString__init(qstrurl, url_qchar, url_len);
  1072. }
  1073. uint8_t _qurl[32]; // sizeof(QUrl) == 8
  1074. QUrl* const qurl(reinterpret_cast<QUrl*>(_qurl));
  1075. QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */);
  1076. uint8_t _webview[128]; // sizeof(QWebEngineView) == 56
  1077. QWebEngineView* const webview = reinterpret_cast<QWebEngineView*>(_webview);
  1078. QWebEngineView__init(webview, nullptr);
  1079. QWebEngineView_move(webview, QPoint(0, kVerticalOffset));
  1080. QWebEngineView_resize(webview, QSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset));
  1081. QWebEngineView_winId(webview);
  1082. QWindow_setParent(QWebEngineView_windowHandle(webview), QWindow_fromWinId(winId));
  1083. QWebEngineView_setUrl(webview, *qurl);
  1084. QWebEngineView_show(webview);
  1085. reloadFn = [=](){
  1086. QWebEngineView_setUrl(webview, *qurl);
  1087. };
  1088. terminateFn = [=](){
  1089. // TODO
  1090. };
  1091. QApplication_exec();
  1092. dlclose(lib);
  1093. return true;
  1094. }
  1095. // -----------------------------------------------------------------------------------------------------------
  1096. // qt6webengine variant (same as qt5 but `QString__init_t` has different arguments)
  1097. static bool qt6webengine(const Window winId, const double scaleFactor, const char* const url)
  1098. {
  1099. void* lib;
  1100. if ((lib = dlopen("libQt6WebEngineWidgets.so.6", RTLD_NOW|RTLD_GLOBAL)) == nullptr ||
  1101. (lib = dlopen("libQt6WebEngineWidgets.so", RTLD_NOW|RTLD_GLOBAL)) == nullptr)
  1102. return false;
  1103. using QApplication__init_t = void (*)(QApplication*, int&, char**, int);
  1104. using QApplication_exec_t = void (*)();
  1105. using QApplication_setAttribute_t = void (*)(Qt::ApplicationAttribute, bool);
  1106. using QString__init_t = void (*)(void*, const QChar*, long long);
  1107. using QUrl__init_t = void (*)(void*, const QString&, int /* QUrl::ParsingMode */);
  1108. using QWebEngineView__init_t = void (*)(QWebEngineView*, void*);
  1109. using QWebEngineView_move_t = void (*)(QWebEngineView*, const QPoint&);
  1110. using QWebEngineView_resize_t = void (*)(QWebEngineView*, const QSize&);
  1111. using QWebEngineView_setUrl_t = void (*)(QWebEngineView*, const QUrl&);
  1112. using QWebEngineView_show_t = void (*)(QWebEngineView*);
  1113. using QWebEngineView_winId_t = ulonglong (*)(QWebEngineView*);
  1114. using QWebEngineView_windowHandle_t = QWindow* (*)(QWebEngineView*);
  1115. using QWindow_fromWinId_t = QWindow* (*)(ulonglong);
  1116. using QWindow_setParent_t = void (*)(QWindow*, void*);
  1117. CPPSYM(QApplication__init_t, QApplication__init, _ZN12QApplicationC1ERiPPci)
  1118. CPPSYM(QApplication_exec_t, QApplication_exec, _ZN15QGuiApplication4execEv)
  1119. CPPSYM(QApplication_setAttribute_t, QApplication_setAttribute, _ZN16QCoreApplication12setAttributeEN2Qt20ApplicationAttributeEb)
  1120. CPPSYM(QString__init_t, QString__init, _ZN7QStringC2EPK5QCharx)
  1121. CPPSYM(QUrl__init_t, QUrl__init, _ZN4QUrlC1ERK7QStringNS_11ParsingModeE)
  1122. CPPSYM(QWebEngineView__init_t, QWebEngineView__init, _ZN14QWebEngineViewC1EP7QWidget)
  1123. CPPSYM(QWebEngineView_move_t, QWebEngineView_move, _ZN7QWidget4moveERK6QPoint)
  1124. CPPSYM(QWebEngineView_resize_t, QWebEngineView_resize, _ZN7QWidget6resizeERK5QSize)
  1125. CPPSYM(QWebEngineView_setUrl_t, QWebEngineView_setUrl, _ZN14QWebEngineView6setUrlERK4QUrl)
  1126. CPPSYM(QWebEngineView_show_t, QWebEngineView_show, _ZN7QWidget4showEv)
  1127. CPPSYM(QWebEngineView_winId_t, QWebEngineView_winId, _ZNK7QWidget5winIdEv)
  1128. CPPSYM(QWebEngineView_windowHandle_t, QWebEngineView_windowHandle, _ZNK7QWidget12windowHandleEv)
  1129. CPPSYM(QWindow_fromWinId_t, QWindow_fromWinId, _ZN7QWindow9fromWinIdEy)
  1130. CPPSYM(QWindow_setParent_t, QWindow_setParent, _ZN7QWindow9setParentEPS_)
  1131. unsetenv("QT_FONT_DPI");
  1132. unsetenv("QT_SCREEN_SCALE_FACTORS");
  1133. unsetenv("QT_USE_PHYSICAL_DPI");
  1134. setenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0", 1);
  1135. char scale[8] = {};
  1136. std::snprintf(scale, 7, "%.2f", scaleFactor);
  1137. setenv("QT_SCALE_FACTOR", scale, 1);
  1138. QApplication_setAttribute(Qt::AA_X11InitThreads, true);
  1139. QApplication_setAttribute(Qt::AA_EnableHighDpiScaling, true);
  1140. QApplication_setAttribute(Qt::AA_UseHighDpiPixmaps, true);
  1141. static int argc = 0;
  1142. static char* argv[] = { nullptr };
  1143. uint8_t _app[64]; // sizeof(QApplication) == 16
  1144. QApplication* const app = reinterpret_cast<QApplication*>(_app);
  1145. QApplication__init(app, argc, argv, 0);
  1146. uint8_t _qstrurl[32]; // sizeof(QString) == 8
  1147. QString* const qstrurl(reinterpret_cast<QString*>(_qstrurl));
  1148. {
  1149. const size_t url_len = std::strlen(url);
  1150. QChar* const url_qchar = new QChar[url_len + 1];
  1151. for (size_t i = 0; i < url_len; ++i)
  1152. url_qchar[i] = QChar(url[i]);
  1153. url_qchar[url_len] = 0;
  1154. QString__init(qstrurl, url_qchar, url_len);
  1155. }
  1156. uint8_t _qurl[32]; // sizeof(QUrl) == 8
  1157. QUrl* const qurl(reinterpret_cast<QUrl*>(_qurl));
  1158. QUrl__init(qurl, *qstrurl, 1 /* QUrl::StrictMode */);
  1159. uint8_t _webview[128]; // sizeof(QWebEngineView) == 56
  1160. QWebEngineView* const webview = reinterpret_cast<QWebEngineView*>(_webview);
  1161. QWebEngineView__init(webview, nullptr);
  1162. QWebEngineView_move(webview, QPoint(0, kVerticalOffset));
  1163. QWebEngineView_resize(webview, QSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT - kVerticalOffset));
  1164. QWebEngineView_winId(webview);
  1165. QWindow_setParent(QWebEngineView_windowHandle(webview), QWindow_fromWinId(winId));
  1166. QWebEngineView_setUrl(webview, *qurl);
  1167. QWebEngineView_show(webview);
  1168. reloadFn = [=](){
  1169. QWebEngineView_setUrl(webview, *qurl);
  1170. };
  1171. terminateFn = [=](){
  1172. // TODO
  1173. };
  1174. QApplication_exec();
  1175. dlclose(lib);
  1176. return true;
  1177. }
  1178. #endif
  1179. // -----------------------------------------------------------------------------------------------------------
  1180. // startup via ld-linux
  1181. static void signalHandler(const int sig)
  1182. {
  1183. switch (sig)
  1184. {
  1185. case SIGTERM:
  1186. terminateFn();
  1187. break;
  1188. }
  1189. }
  1190. static void* threadHandler(void* const ptr)
  1191. {
  1192. WebViewRingBuffer* const shmptr = static_cast<WebViewRingBuffer*>(ptr);
  1193. // TODO wait until page is loaded, or something better
  1194. d_sleep(1);
  1195. while (shmptr->valid)
  1196. {
  1197. if (webview_timedwait(&shmptr->client.sem))
  1198. wakeFn(shmptr);
  1199. }
  1200. return nullptr;
  1201. }
  1202. int dpf_webview_start(const int argc, char* argv[])
  1203. {
  1204. d_stdout("started %d %s", argc, argv[1]);
  1205. if (argc != 3)
  1206. {
  1207. d_stderr("WebView entry point, nothing to see here! ;)");
  1208. return 1;
  1209. }
  1210. uselocale(newlocale(LC_NUMERIC_MASK, "C", nullptr));
  1211. Display* const display = XOpenDisplay(nullptr);
  1212. DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1);
  1213. const char* const shmname = argv[2];
  1214. const int shmfd = shm_open(shmname, O_RDWR, 0);
  1215. if (shmfd < 0)
  1216. {
  1217. d_stderr("shm_open failed: %s", std::strerror(errno));
  1218. return 1;
  1219. }
  1220. WebViewRingBuffer* const shmptr = static_cast<WebViewRingBuffer*>(mmap(nullptr,
  1221. sizeof(WebViewRingBuffer),
  1222. PROT_READ|PROT_WRITE,
  1223. MAP_SHARED,
  1224. shmfd, 0));
  1225. if (shmptr == nullptr || shmptr == nullptr)
  1226. {
  1227. d_stderr("mmap failed: %s", std::strerror(errno));
  1228. close(shmfd);
  1229. return 1;
  1230. }
  1231. RingBufferControl<WebViewSharedBuffer> rbctrl;
  1232. rbctrl.setRingBuffer(&shmptr->client, false);
  1233. // fetch initial data
  1234. bool hasInitialData = false;
  1235. Window winId = 0;
  1236. uint width = 0, height = 0;
  1237. double scaleFactor = 0;
  1238. int x = 0, y = 0;
  1239. char* url = nullptr;
  1240. char* initJS = nullptr;
  1241. while (shmptr->valid && webview_timedwait(&shmptr->client.sem))
  1242. {
  1243. if (rbctrl.isDataAvailableForReading())
  1244. {
  1245. DISTRHO_SAFE_ASSERT_RETURN(rbctrl.readUInt() == kWebViewMessageInitData, 1);
  1246. hasInitialData = true;
  1247. winId = rbctrl.readULong();
  1248. width = rbctrl.readUInt();
  1249. height = rbctrl.readUInt();
  1250. scaleFactor = rbctrl.readDouble();
  1251. x = rbctrl.readInt();
  1252. y = rbctrl.readInt();
  1253. const uint urllen = rbctrl.readUInt();
  1254. url = static_cast<char*>(std::malloc(urllen));
  1255. rbctrl.readCustomData(url, urllen);
  1256. if (const uint initjslen = rbctrl.readUInt())
  1257. {
  1258. initJS = static_cast<char*>(std::malloc(initjslen));
  1259. rbctrl.readCustomData(initJS, initjslen);
  1260. }
  1261. }
  1262. }
  1263. pthread_t thread;
  1264. if (hasInitialData && pthread_create(&thread, nullptr, threadHandler, shmptr) == 0)
  1265. {
  1266. struct sigaction sig = {};
  1267. sig.sa_handler = signalHandler;
  1268. sig.sa_flags = SA_RESTART;
  1269. sigemptyset(&sig.sa_mask);
  1270. sigaction(SIGTERM, &sig, nullptr);
  1271. // qt5webengine(winId, scaleFactor, url) ||
  1272. // qt6webengine(winId, scaleFactor, url) ||
  1273. gtk3(display, winId, x, y, width, height, scaleFactor, url, initJS, shmptr);
  1274. shmptr->valid = false;
  1275. pthread_join(thread, nullptr);
  1276. }
  1277. munmap(shmptr, sizeof(WebViewRingBuffer));
  1278. close(shmfd);
  1279. XCloseDisplay(display);
  1280. return 0;
  1281. }
  1282. // --------------------------------------------------------------------------------------------------------------------
  1283. #endif // WEB_VIEW_USING_X11_IPC
  1284. #ifdef WEB_VIEW_DGL_NAMESPACE
  1285. END_NAMESPACE_DGL
  1286. #else
  1287. END_NAMESPACE_DISTRHO
  1288. #endif
  1289. #undef MACRO_NAME
  1290. #undef MACRO_NAME2
  1291. #undef WEB_VIEW_DISTRHO_NAMESPACE
  1292. #undef WEB_VIEW_DGL_NAMESPACE