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.

1541 lines
52KB

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