1 /++ 2 A webview (based on [arsd.webview]) for minigui. 3 4 For now at least, to use this, you MUST have a [WebViewApp] in scope in main for the duration of your gui application. 5 6 Warning: CEF spams the current directory with a bunch of files and directories. You might want to run your program in a dedicated location. 7 8 History: 9 Added November 5, 2021. NOT YET STABLE. 10 11 status text and favicon change notifications implemented on Windows WebView2 on December 16, 2023 (so long as the necessary api version is available, otherwise it will silently drop it). 12 13 Dependencies: 14 Requires arsd.png on Windows for favicons, may require more in the future. 15 16 Examples: 17 --- 18 /+ dub.sdl: 19 name "web" 20 dependency "arsd-official:minigui-webview" version="*" 21 +/ 22 23 import arsd.minigui; 24 import arsd.minigui_addons.webview; 25 26 void main() { 27 auto app = WebViewApp(null); 28 auto window = new Window; 29 auto webview = new WebViewWidget("http://dlang.org/", window); 30 window.loop; 31 } 32 --- 33 +/ 34 module arsd.minigui_addons.webview; 35 // FIXME: i think i can download the cef automatically if needed. 36 37 // want to add mute support 38 // https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2_8?view=webview2-1.0.2210.55 39 40 // javascript : AddScriptToExecuteOnDocumentCreated / cef_register_extension or https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md idk 41 // need a: post web message / on web message posted 42 43 // and some magic reply to certain url schemes. 44 45 // also want to make sure it can prefix http:// and such when typing in a url bar cross platform 46 47 import arsd.core; 48 49 version(linux) 50 version=cef; 51 version(Windows) 52 version=wv2; 53 54 /+ 55 SPA mode: put favicon on top level window, no other user controls at top level, links to different domains always open in new window. 56 +/ 57 58 // FIXME: look in /opt/cef for the dll and the locales 59 60 import arsd.minigui; 61 import arsd.webview; 62 63 version(wv2) { 64 alias WebViewWidget = WebViewWidget_WV2; 65 alias WebViewApp = Wv2App; 66 } else version(cef) { 67 alias WebViewWidget = WebViewWidget_CEF; 68 alias WebViewApp = CefApp; 69 } else static assert(0, "no webview available"); 70 71 class WebViewWidgetBase : NestedChildWindowWidget { 72 protected SimpleWindow containerWindow; 73 74 protected this(Widget parent) { 75 containerWindow = new SimpleWindow(640, 480, null, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent)); 76 // import std.stdio; writefln("container window %d created", containerWindow.window); 77 78 super(containerWindow, parent); 79 } 80 81 mixin Observable!(string, "title"); 82 mixin Observable!(string, "url"); 83 mixin Observable!(string, "status"); 84 85 // not implemented on WV2 86 mixin Observable!(int, "loadingProgress"); 87 88 // not implemented on WV2 89 mixin Observable!(string, "favicon_url"); 90 mixin Observable!(MemoryImage, "favicon"); // please note it can be changed to null! 91 92 abstract void refresh(); 93 abstract void back(); 94 abstract void forward(); 95 abstract void stop(); 96 97 abstract void navigate(string url); 98 99 // the url and line are for error reporting purposes. They might be ignored. 100 // FIXME: add a callback with the reply. this can send a message from the js thread in cef and just ExecuteScript inWV2 101 // FIXME: add AddScriptToExecuteOnDocumentCreated for cef.... 102 abstract void executeJavascript(string code, string url = null, int line = 0); 103 // for injecting stuff into the context 104 // abstract void executeJavascriptBeforeEachLoad(string code); 105 106 abstract void showDevTools(); 107 108 /++ 109 Your communication consists of running Javascript and sending string messages back and forth, 110 kinda similar to your communication with a web server. 111 +/ 112 // these form your communcation channel between the web view and the native world 113 // abstract void sendMessageToHost(string json); 114 // void delegate(string json) receiveMessageFromHost; 115 116 /+ 117 I also need a url filter 118 +/ 119 120 // this is implemented as a do-nothing in the NestedChildWindowWidget base 121 // but you will almost certainly need to override it in implementations. 122 // abstract void registerMovementAdditionalWork(); 123 } 124 125 // AddScriptToExecuteOnDocumentCreated 126 127 128 129 version(wv2) 130 class WebViewWidget_WV2 : WebViewWidgetBase { 131 private RC!ICoreWebView2 webview_window; 132 // 12 introduces status bar 133 // 15 introduces favicon notifications 134 // 16 introduces printing 135 private RC!ICoreWebView2_16 webview_window_ext_1; 136 private RC!ICoreWebView2Environment webview_env; 137 private RC!ICoreWebView2Controller controller; 138 139 private bool initialized; 140 141 private HRESULT initializeWithController(ICoreWebView2Controller controller_raw) { 142 143 // need to keep this beyond the callback or we're doomed. 144 this.controller = RC!ICoreWebView2Controller(controller_raw); 145 146 this.webview_window = controller.CoreWebView2; 147 148 this.webview_window_ext_1 = this.webview_window.queryInterface!(ICoreWebView2_16); 149 150 bool enableStatusBar = true; 151 152 if(this.webview_window_ext_1) { 153 enableStatusBar = false; 154 this.webview_window_ext_1.add_StatusBarTextChanged!(typeof(this))((sender, args, this_) { 155 this_.status = toGC(&this_.webview_window_ext_1.raw.get_StatusBarText); 156 return S_OK; 157 }, this); 158 159 webview_window_ext_1.add_FaviconChanged!(typeof(this))((sender, args, this_) { 160 this_.webview_window_ext_1.GetFavicon( 161 COREWEBVIEW2_FAVICON_IMAGE_FORMAT_PNG, 162 callback!(ICoreWebView2GetFaviconCompletedHandler, typeof(this_))(function(error, streamPtrConst, ctx2) { 163 164 auto streamPtr = cast(IStream) streamPtrConst; 165 166 ubyte[] buffer = new ubyte[](640); // most favicons are pretty small 167 enum growth_size = 1024; // and we'll grow linearly by the kilobyte 168 size_t at; 169 170 more: 171 ULONG actuallyRead; 172 auto ret = streamPtr.Read(buffer.ptr + at, cast(UINT) (buffer.length - at), &actuallyRead); 173 if(ret == S_OK) { 174 // read completed, possibly more data pending 175 auto moreData = actuallyRead >= (buffer.length - at); 176 177 at += actuallyRead; 178 if(moreData && (buffer.length - at < growth_size)) 179 buffer.length += growth_size; 180 goto more; 181 } else if(ret == S_FALSE) { 182 // end of file reached 183 at += actuallyRead; 184 buffer = buffer[0 .. at]; 185 186 import arsd.png; 187 ctx2.favicon = readPngFromBytes(buffer); 188 } else { 189 // other error 190 throw new ComException(ret); 191 } 192 193 return S_OK; 194 }, this_) 195 ); 196 197 return S_OK; 198 }, this); 199 } 200 201 webview_window.add_DocumentTitleChanged!(typeof(this))((sender, args, this_) { 202 this_.title = toGC(&sender.get_DocumentTitle); 203 return S_OK; 204 }, this); 205 206 webview_window.add_NewWindowRequested!(typeof(this))((sender, args, this_) { 207 // args.get_Uri 208 // args.get_IsUserInitiated 209 // args.put_NewWindow(); 210 211 string url = toGC(&args.get_Uri); 212 int ret; 213 214 WebViewWidget_WV2 widget; 215 216 runInGuiThread({ 217 ret = 0; 218 219 scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { 220 ret = 1; 221 if(parent !is null) { 222 auto widget = new WebViewWidget_WV2(url, this_.openNewWindow, passed_settings, parent); 223 224 return widget; 225 } 226 return null; 227 }; 228 this_.openNewWindow(OpenNewWindowParams(url, accept)); 229 return; 230 }); 231 232 if(ret) { 233 args.put_Handled(true); 234 // args.put_NewWindow(widget.webview_window.returnable); 235 } 236 237 return S_OK; 238 239 }, this); 240 241 // add_HistoryChanged 242 // that's where CanGoBack and CanGoForward can be rechecked. 243 244 RC!ICoreWebView2Settings Settings = this.webview_window.Settings; 245 Settings.IsScriptEnabled = TRUE; 246 Settings.AreDefaultScriptDialogsEnabled = TRUE; 247 Settings.IsWebMessageEnabled = TRUE; 248 Settings.IsStatusBarEnabled = enableStatusBar; 249 250 auto ert = this.webview_window.add_NavigationStarting!(typeof(this))( 251 function (sender, args, this_) { 252 this_.url = toGC(&args.get_Uri); 253 return S_OK; 254 }, this); 255 256 RECT bounds; 257 GetClientRect(this.containerWindow.impl.hwnd, &bounds); 258 controller.Bounds = bounds; 259 //error = webview_window.Navigate("http://arsdnet.net/test.html"w.ptr); 260 //error = webview_window.NavigateToString("<html><body>Hello</body></html>"w.ptr); 261 //error = webview_window.Navigate("http://192.168.1.10/"w.ptr); 262 263 if(url !is null) { 264 WCharzBuffer bfr = WCharzBuffer(url); 265 this.webview_window.Navigate(bfr.ptr); 266 } 267 268 controller.IsVisible = true; 269 270 this.initialized = true; 271 272 return S_OK; 273 } 274 275 private void delegate(scope OpenNewWindowParams) openNewWindow; 276 277 this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) { 278 this.openNewWindow = openNewWindow; 279 super(parent); 280 // that ctor sets containerWindow 281 282 this.url = url; 283 284 Wv2App.useEnvironment((env) { 285 env.CreateCoreWebView2Controller(containerWindow.impl.hwnd, 286 callback!(ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, typeof(this))(function(error, controller_raw, ctx) { 287 if(error || controller_raw is null) 288 return error; 289 290 return ctx.initializeWithController(controller_raw); 291 }, this)); 292 }); 293 } 294 295 override void registerMovementAdditionalWork() { 296 if(initialized) { 297 RECT bounds; 298 GetClientRect(containerWindow.impl.hwnd, &bounds); 299 controller.Bounds = bounds; 300 301 controller.NotifyParentWindowPositionChanged(); 302 } 303 } 304 305 override void refresh() { 306 if(!initialized) return; 307 webview_window.Reload(); 308 } 309 override void back() { 310 if(!initialized) return; 311 webview_window.GoBack(); 312 } 313 override void forward() { 314 if(!initialized) return; 315 webview_window.GoForward(); 316 } 317 override void stop() { 318 if(!initialized) return; 319 webview_window.Stop(); 320 } 321 322 override void navigate(string url) { 323 if(!initialized) return; 324 import std.utf; 325 auto error = webview_window.Navigate(url.toUTF16z); 326 } 327 328 // the url and line are for error reporting purposes 329 override void executeJavascript(string code, string url = null, int line = 0) { 330 if(!initialized) return; 331 import std.utf; 332 webview_window.ExecuteScript(code.toUTF16z, null); 333 } 334 335 override void showDevTools() { 336 if(!initialized) return; 337 webview_window.OpenDevToolsWindow(); 338 } 339 } 340 341 /++ 342 The openInNewWindow delegate is given these params. 343 344 To accept the new window, call 345 346 params.accept(parent_widget); 347 348 Please note, you can force it to replace the current tab 349 by just navigating your current thing to the given url instead 350 of accepting it. 351 352 If you accept with a null widget, it will create a new window 353 but then return null, since the new window is managed by the 354 underlying webview instead of by minigui. 355 356 If you do not call accept, the pop up will be blocked. 357 358 accept returns an instance to the newly created widget, which will 359 be a parent to the widget you passed. 360 361 accept will be called from the gui thread and it MUST not call into 362 any other webview methods. It should only create windows/widgets 363 and set event handlers etc. 364 365 You MUST not escape references to anything in this structure. It 366 is entirely strictly temporary! 367 +/ 368 struct OpenNewWindowParams { 369 string url; 370 WebViewWidget delegate(Widget parent, BrowserSettings settings = BrowserSettings.init) accept; 371 } 372 373 /++ 374 Represents a browser setting that can be left default or specifically turned on or off. 375 +/ 376 struct SettingValue { 377 private byte value = -1; 378 379 /++ 380 Set it with `= true` or `= false`. 381 +/ 382 void opAssign(bool enable) { 383 value = enable ? 1 : 0; 384 } 385 386 /++ 387 And this resets it to the default value. 388 +/ 389 void setDefault() { 390 value = -1; 391 } 392 393 /// If isDefault, it will use the default setting from the browser. Else, the getValue return value will be used. getValue is invalid if !isDefault. 394 bool isDefault() { 395 return value == -1; 396 } 397 398 /// ditto 399 bool getValue() { 400 return value == 1; 401 } 402 } 403 404 /++ 405 Defines settings for a browser widget. Not all backends will respect all settings. 406 407 The order of members of this struct may change at any time. Refer to its members by 408 name. 409 +/ 410 struct BrowserSettings { 411 /// This is just disabling the automatic positional constructor, since that is not stable here. 412 this(typeof(null)) {} 413 414 string standardFontFamily; 415 string fixedFontFamily; 416 string serifFontFamily; 417 string sansSerifFontFamily; 418 string cursiveFontFamily; 419 string fantasyFontFamily; 420 421 int defaultFontSize; 422 int defaultFixedFontSize; 423 int minimumFontSize; 424 //int minimumLogicalFontSize; 425 426 SettingValue remoteFontsEnabled; 427 SettingValue javascriptEnabled; 428 SettingValue imagesEnabled; 429 SettingValue clipboardAccessEnabled; 430 SettingValue localStorageEnabled; 431 432 version(cef) 433 private void set(cef_browser_settings_t* browser_settings) { 434 alias settings = this; 435 if(settings.standardFontFamily) 436 browser_settings.standard_font_family = cef_string_t(settings.standardFontFamily); 437 if(settings.fixedFontFamily) 438 browser_settings.fixed_font_family = cef_string_t(settings.fixedFontFamily); 439 if(settings.serifFontFamily) 440 browser_settings.serif_font_family = cef_string_t(settings.serifFontFamily); 441 if(settings.sansSerifFontFamily) 442 browser_settings.sans_serif_font_family = cef_string_t(settings.sansSerifFontFamily); 443 if(settings.cursiveFontFamily) 444 browser_settings.cursive_font_family = cef_string_t(settings.cursiveFontFamily); 445 if(settings.fantasyFontFamily) 446 browser_settings.fantasy_font_family = cef_string_t(settings.fantasyFontFamily); 447 if(settings.defaultFontSize) 448 browser_settings.default_font_size = settings.defaultFontSize; 449 if(settings.defaultFixedFontSize) 450 browser_settings.default_fixed_font_size = settings.defaultFixedFontSize; 451 if(settings.minimumFontSize) 452 browser_settings.minimum_font_size = settings.minimumFontSize; 453 454 if(!settings.remoteFontsEnabled.isDefault()) 455 browser_settings.remote_fonts = settings.remoteFontsEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED; 456 if(!settings.javascriptEnabled.isDefault()) 457 browser_settings.javascript = settings.javascriptEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED; 458 if(!settings.imagesEnabled.isDefault()) 459 browser_settings.image_loading = settings.imagesEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED; 460 if(!settings.clipboardAccessEnabled.isDefault()) 461 browser_settings.javascript_access_clipboard = settings.clipboardAccessEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED; 462 if(!settings.localStorageEnabled.isDefault()) 463 browser_settings.local_storage = settings.localStorageEnabled.getValue() ? cef_state_t.STATE_ENABLED : cef_state_t.STATE_DISABLED; 464 465 } 466 } 467 468 version(cef) 469 class WebViewWidget_CEF : WebViewWidgetBase { 470 /++ 471 Create a webview that does not support opening links in new windows and uses default settings to load the given url. 472 +/ 473 this(string url, Widget parent) { 474 this(url, null, BrowserSettings.init, parent); 475 } 476 477 /++ 478 Full-featured constructor. 479 +/ 480 this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, BrowserSettings settings, Widget parent) { 481 //semaphore = new Semaphore; 482 assert(CefApp.active); 483 484 this(new MiniguiCefClient(openNewWindow), parent, false); 485 486 cef_window_info_t window_info; 487 window_info.parent_window = containerWindow.nativeWindowHandle; 488 489 cef_string_t cef_url = cef_string_t(url);//"http://arsdnet.net/test.html"); 490 491 cef_browser_settings_t browser_settings; 492 browser_settings.size = cef_browser_settings_t.sizeof; 493 494 settings.set(&browser_settings); 495 496 auto got = libcef.browser_host_create_browser(&window_info, client.passable, &cef_url, &browser_settings, null, null); 497 } 498 499 /+ 500 ~this() { 501 import core.stdc.stdio; 502 import core.memory; 503 printf("CLEANUP %s\n", GC.inFinalizer ? "GC".ptr : "destroy".ptr); 504 } 505 +/ 506 507 override void dispose() { 508 // sdpyPrintDebugString("closed"); 509 // the window is already gone so too late to do this really.... 510 // if(browserHandle) browserHandle.get_host.close_browser(true); 511 512 // sdpyPrintDebugString("DISPOSE"); 513 514 if(win && win.nativeWindowHandle()) 515 mapping.remove(win.nativeWindowHandle()); 516 if(browserWindow) 517 browserMapping.remove(browserWindow); 518 519 .destroy(this); // but this is ok to do some memory management cleanup 520 } 521 522 private this(MiniguiCefClient client, Widget parent, bool isDevTools) { 523 super(parent); 524 525 this.client = client; 526 527 flushGui(); 528 529 mapping[containerWindow.nativeWindowHandle()] = this; 530 531 this.addEventListener(delegate(KeyDownEvent ke) { 532 if(ke.key == Key.Tab) 533 ke.preventDefault(); 534 }); 535 536 this.addEventListener((FocusEvent fe) { 537 if(!browserHandle) return; 538 539 XFocusChangeEvent ev; 540 ev.type = arsd.simpledisplay.EventType.FocusIn; 541 ev.display = XDisplayConnection.get; 542 ev.window = ozone; 543 ev.mode = NotifyModes.NotifyNormal; 544 ev.detail = NotifyDetail.NotifyVirtual; 545 546 trapXErrors( { 547 XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); 548 }); 549 550 // this also works if the message is buggy and it avoids weirdness from raising window etc 551 //executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));"); 552 }); 553 this.addEventListener((BlurEvent be) { 554 if(!browserHandle) return; 555 556 XFocusChangeEvent ev; 557 ev.type = arsd.simpledisplay.EventType.FocusOut; 558 ev.display = XDisplayConnection.get; 559 ev.window = ozone; 560 ev.mode = NotifyModes.NotifyNormal; 561 ev.detail = NotifyDetail.NotifyNonlinearVirtual; 562 563 trapXErrors( { 564 XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); 565 }); 566 567 //executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));"); 568 }); 569 570 bool closeAttempted = false; 571 572 if(isDevTools) 573 this.parentWindow.addEventListener((scope ClosingEvent ce) { 574 this.parentWindow.hide(); 575 ce.preventDefault(); 576 }); 577 else 578 this.parentWindow.addEventListener((scope ClosingEvent ce) { 579 if(devTools) 580 devTools.close(); 581 if(browserHandle) { 582 if(!closeAttempted) { 583 closeAttempted = true; 584 browserHandle.get_host.close_browser(false); 585 ce.preventDefault(); 586 sdpyPrintDebugString("closing 1"); 587 } else { 588 browserHandle.get_host.close_browser(true); 589 sdpyPrintDebugString("closing 2"); 590 } 591 } 592 }); 593 } 594 595 ~this() { 596 import core.stdc.stdio; 597 printf("GC'd %p\n", cast(void*) this); 598 } 599 600 private MiniguiCefClient client; 601 602 override void registerMovementAdditionalWork() { 603 if(browserWindow) { 604 // import std.stdio; writeln("new size ", width, "x", height); 605 static if(UsingSimpledisplayX11) { 606 XResizeWindow(XDisplayConnection.get, browserWindow, width, height); 607 if(ozone) XResizeWindow(XDisplayConnection.get, ozone, width, height); 608 } 609 // FIXME: do for Windows too 610 } 611 } 612 613 SimpleWindow browserHostWrapped; 614 SimpleWindow browserWindowWrapped; 615 override SimpleWindow focusableWindow() { 616 if(browserWindowWrapped is null && browserWindow) { 617 browserWindowWrapped = new SimpleWindow(browserWindow); 618 // FIXME: this should never actually happen should it 619 } 620 return browserWindowWrapped; 621 } 622 623 private NativeWindowHandle browserWindow; 624 private NativeWindowHandle ozone; 625 private RC!cef_browser_t browserHandle; 626 627 private static WebViewWidget[NativeWindowHandle] mapping; 628 private static WebViewWidget[NativeWindowHandle] browserMapping; 629 630 private { 631 string findingText; 632 bool findingCase; 633 } 634 635 // might not be stable, webview does this fully integrated 636 void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) { 637 if(browserHandle) { 638 auto host = browserHandle.get_host(); 639 640 auto txt = cef_string_t(text); 641 host.find(&txt, forward, matchCase, findNext); 642 643 findingText = text; 644 findingCase = matchCase; 645 } 646 } 647 648 // ditto 649 void findPrevious() { 650 if(!browserHandle) 651 return; 652 auto host = browserHandle.get_host(); 653 auto txt = cef_string_t(findingText); 654 host.find(&txt, 0, findingCase, 1); 655 } 656 657 // ditto 658 void findNext() { 659 if(!browserHandle) 660 return; 661 auto host = browserHandle.get_host(); 662 auto txt = cef_string_t(findingText); 663 host.find(&txt, 1, findingCase, 1); 664 } 665 666 // ditto 667 void stopFind() { 668 if(!browserHandle) 669 return; 670 auto host = browserHandle.get_host(); 671 host.stop_finding(1); 672 } 673 674 override void refresh() { if(browserHandle) browserHandle.reload(); } 675 override void back() { if(browserHandle) browserHandle.go_back(); } 676 override void forward() { if(browserHandle) browserHandle.go_forward(); } 677 override void stop() { if(browserHandle) browserHandle.stop_load(); } 678 679 override void navigate(string url) { 680 if(!browserHandle) return; 681 auto s = cef_string_t(url); 682 browserHandle.get_main_frame.load_url(&s); 683 } 684 685 // the url and line are for error reporting purposes 686 override void executeJavascript(string code, string url = null, int line = 0) { 687 if(!browserHandle) return; 688 689 auto c = cef_string_t(code); 690 auto u = cef_string_t(url); 691 browserHandle.get_main_frame.execute_java_script(&c, &u, line); 692 } 693 694 private Window devTools; 695 override void showDevTools() { 696 if(!browserHandle) return; 697 698 if(devTools is null) { 699 auto host = browserHandle.get_host; 700 701 if(host.has_dev_tools()) { 702 host.close_dev_tools(); 703 return; 704 } 705 706 cef_window_info_t windowinfo; 707 version(linux) { 708 auto sw = new Window("DevTools"); 709 //sw.win.beingOpenKeepsAppOpen = false; 710 devTools = sw; 711 712 auto wv = new WebViewWidget_CEF(client, sw, true); 713 714 sw.show(); 715 716 windowinfo.parent_window = wv.containerWindow.nativeWindowHandle; 717 } 718 host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */); 719 } else { 720 if(devTools.hidden) 721 devTools.show(); 722 else 723 devTools.hide(); 724 } 725 } 726 727 // FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries. 728 729 // JS on init? 730 // JS bindings? 731 // user styles? 732 // navigate to string? (can just use a data uri maybe?) 733 // custom scheme handlers? 734 735 // navigation callbacks to prohibit certain things or move links to new window etc? 736 } 737 738 version(cef) { 739 740 //import core.sync.semaphore; 741 //__gshared Semaphore semaphore; 742 743 /+ 744 Finds the WebViewWidget associated with the given browser, then runs the given code in the gui thread on it. 745 +/ 746 void runOnWebView(RC!cef_browser_t browser, void delegate(WebViewWidget) dg) nothrow { 747 auto wh = cast(NativeWindowHandle) browser.get_host.get_window_handle; 748 749 import core.thread; 750 try { thread_attachThis(); } catch(Exception e) {} 751 752 runInGuiThreadAsync({ 753 if(auto wvp = wh in WebViewWidget.browserMapping) { 754 dg(*wvp); 755 } else { 756 writeln("not found ", wh, WebViewWidget.browserMapping); 757 } 758 }); 759 } 760 761 class MiniguiCefLifeSpanHandler : CEF!cef_life_span_handler_t { 762 private MiniguiCefClient client; 763 this(MiniguiCefClient client) { 764 this.client = client; 765 } 766 767 override int on_before_popup( 768 RC!cef_browser_t browser, 769 RC!cef_frame_t frame, 770 const(cef_string_t)* target_url, 771 const(cef_string_t)* target_frame_name, 772 cef_window_open_disposition_t target_disposition, 773 int user_gesture, 774 const(cef_popup_features_t)* popupFeatures, 775 cef_window_info_t* windowInfo, 776 cef_client_t** client, 777 cef_browser_settings_t* browser_settings, 778 cef_dictionary_value_t** extra_info, 779 int* no_javascript_access 780 ) { 781 sdpyPrintDebugString("on_before_popup"); 782 if(this.client.openNewWindow is null) 783 return 1; // new windows disabled 784 785 try { 786 int ret; 787 788 import core.thread; 789 try { thread_attachThis(); } catch(Exception e) {} 790 791 // FIXME: change settings here 792 793 runInGuiThread({ 794 ret = 1; 795 scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { 796 ret = 0; 797 if(parent !is null) { 798 auto widget = new WebViewWidget_CEF(this.client, parent, false); 799 (*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle; 800 801 passed_settings.set(browser_settings); 802 803 return widget; 804 } 805 return null; 806 }; 807 this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept)); 808 return; 809 }); 810 811 return ret; 812 } catch(Exception e) { 813 return 1; 814 } 815 /+ 816 if(!user_gesture) 817 return 1; // if not created by the user, cancel it; basic popup blocking 818 +/ 819 } 820 override void on_after_created(RC!cef_browser_t browser) { 821 auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle(); 822 auto ptr = browser.passable; // this adds to the refcount until it gets inside 823 824 import core.thread; 825 try { thread_attachThis(); } catch(Exception e) {} 826 827 // the only reliable key (at least as far as i can tell) is the window handle 828 // so gonna look that up and do the sync mapping that way. 829 runInGuiThreadAsync({ 830 version(Windows) { 831 auto parent = GetParent(handle); 832 } else static if(UsingSimpledisplayX11) { 833 import arsd.simpledisplay : Window; 834 Window root; 835 Window parent; 836 Window ozone; 837 uint c = 0; 838 auto display = XDisplayConnection.get; 839 Window* children; 840 XQueryTree(display, handle, &root, &parent, &children, &c); 841 if(c == 1) 842 ozone = children[0]; 843 XFree(children); 844 } else static assert(0); 845 846 if(auto wvp = parent in WebViewWidget.mapping) { 847 auto wv = *wvp; 848 wv.browserWindow = handle; 849 wv.browserHandle = RC!cef_browser_t(ptr); 850 wv.ozone = ozone ? ozone : handle; 851 852 wv.browserHostWrapped = new SimpleWindow(handle); 853 // XSelectInput(XDisplayConnection.get, handle, EventMask.StructureNotifyMask); 854 855 wv.browserHostWrapped.onDestroyed = delegate{ 856 import std.stdio; writefln("browser host %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 857 858 auto bce = new BrowserClosedEvent(wv); 859 bce.dispatch(); 860 }; 861 862 // need this to forward key events to 863 wv.browserWindowWrapped = new SimpleWindow(wv.ozone); 864 865 /+ 866 XSelectInput(XDisplayConnection.get, wv.ozone, EventMask.StructureNotifyMask); 867 wv.browserWindowWrapped.onDestroyed = delegate{ 868 import std.stdio; writefln("browser core %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 869 870 //auto bce = new BrowserClosedEvent(wv); 871 //bce.dispatch(); 872 }; 873 +/ 874 875 /+ 876 XSelectInput(XDisplayConnection.get, ozone, EventMask.FocusChangeMask); 877 wv.browserWindowWrapped.onFocusChange = (bool got) { 878 import std.format; 879 sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window)); 880 }; 881 +/ 882 883 wv.registerMovementAdditionalWork(); 884 885 WebViewWidget.browserMapping[handle] = wv; 886 } else assert(0); 887 }); 888 } 889 override int do_close(RC!cef_browser_t browser) { 890 import std.stdio; 891 debug writeln("do_close"); 892 /+ 893 browser.runOnWebView((wv) { 894 wv.browserWindowWrapped.close(); 895 .destroy(wv.browserHandle); 896 }); 897 898 return 1; 899 +/ 900 901 return 0; 902 } 903 override void on_before_close(RC!cef_browser_t browser) { 904 import std.stdio; debug writeln("notify"); 905 browser.runOnWebView((wv) { 906 .destroy(wv.browserHandle); 907 }); 908 /+ 909 try 910 semaphore.notify; 911 catch(Exception e) { assert(0); } 912 +/ 913 } 914 } 915 916 class MiniguiLoadHandler : CEF!cef_load_handler_t { 917 override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) { 918 /+ 919 browser.runOnWebView((WebViewWidget wvw) { 920 wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree; 921 }); 922 +/ 923 } 924 override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) { 925 } 926 override void on_load_error(RC!(cef_browser_t), RC!(cef_frame_t), cef_errorcode_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) { 927 } 928 override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) { 929 } 930 } 931 932 class MiniguiDialogHandler : CEF!cef_dialog_handler_t { 933 override int on_file_dialog(RC!(cef_browser_t) browser, cef_file_dialog_mode_t mode, const(cef_string_utf16_t)* title, const(cef_string_utf16_t)* default_file_path, cef_string_list_t accept_filters, RC!(cef_file_dialog_callback_t) callback) { 934 try { 935 auto ptr = callback.passable(); 936 browser.runOnWebView((wv) { 937 getOpenFileName((string name) { 938 auto callback = RC!cef_file_dialog_callback_t(ptr); 939 auto list = libcef.string_list_alloc(); 940 auto item = cef_string_t(name); 941 libcef.string_list_append(list, &item); 942 callback.cont(list); 943 }, null, null, () { 944 auto callback = RC!cef_file_dialog_callback_t(ptr); 945 callback.cancel(); 946 }, "/home/me/"); 947 }); 948 } catch(Exception e) {} 949 950 return 1; 951 } 952 } 953 954 class MiniguiDownloadHandler : CEF!cef_download_handler_t { 955 override void on_before_download( 956 RC!cef_browser_t browser, 957 RC!cef_download_item_t download_item, 958 const(cef_string_t)* suggested_name, 959 RC!cef_before_download_callback_t callback 960 ) nothrow 961 { 962 // FIXME: different filename and check if exists for overwrite etc 963 auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length])); 964 sdpyPrintDebugString(fn.toGC); 965 callback.cont(&fn, false); 966 } 967 968 override void on_download_updated( 969 RC!cef_browser_t browser, 970 RC!cef_download_item_t download_item, 971 RC!cef_download_item_callback_t cancel 972 ) nothrow 973 { 974 sdpyPrintDebugString(download_item.get_percent_complete()); 975 // FIXME 976 } 977 978 override int can_download(RC!(cef_browser_t), const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) { 979 return 1; 980 } 981 } 982 983 class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t { 984 override int on_pre_key_event( 985 RC!(cef_browser_t) browser, 986 const(cef_key_event_t)* event, 987 XEvent* osEvent, 988 int* isShortcut 989 ) nothrow { 990 /+ 991 sdpyPrintDebugString("---pre---"); 992 sdpyPrintDebugString(event.focus_on_editable_field); 993 sdpyPrintDebugString(event.windows_key_code); 994 sdpyPrintDebugString(event.modifiers); 995 sdpyPrintDebugString(event.unmodified_character); 996 +/ 997 //*isShortcut = 1; 998 return 0; // 1 if handled, which cancels sending it to browser 999 } 1000 1001 override int on_key_event( 1002 RC!(cef_browser_t) browser, 1003 const(cef_key_event_t)* event, 1004 XEvent* osEvent 1005 ) nothrow { 1006 /+ 1007 sdpyPrintDebugString("---key---"); 1008 sdpyPrintDebugString(event.focus_on_editable_field); 1009 sdpyPrintDebugString(event.windows_key_code); 1010 sdpyPrintDebugString(event.modifiers); 1011 +/ 1012 return 0; // 1 if handled 1013 } 1014 } 1015 1016 class MiniguiDisplayHandler : CEF!cef_display_handler_t { 1017 override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) { 1018 auto url = address.toGC; 1019 browser.runOnWebView((wv) { 1020 wv.url = url; 1021 }); 1022 } 1023 override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) { 1024 auto t = title.toGC; 1025 browser.runOnWebView((wv) { 1026 wv.title = t; 1027 }); 1028 } 1029 override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) { 1030 string url; 1031 auto size = libcef.string_list_size(urls); 1032 if(size > 0) { 1033 cef_string_t str; 1034 libcef.string_list_value(urls, 0, &str); 1035 url = str.toGC; 1036 1037 static class Thing : CEF!cef_download_image_callback_t { 1038 RC!cef_browser_t browserHandle; 1039 this(RC!cef_browser_t browser) nothrow { 1040 this.browserHandle = browser; 1041 } 1042 override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow { 1043 int width; 1044 int height; 1045 if(image.getRawPointer is null) { 1046 browserHandle.runOnWebView((wv) { 1047 wv.favicon = null; 1048 }); 1049 return; 1050 } 1051 1052 auto data = image.get_as_bitmap(1.0, cef_color_type_t.CEF_COLOR_TYPE_RGBA_8888, cef_alpha_type_t.CEF_ALPHA_TYPE_POSTMULTIPLIED, &width, &height); 1053 1054 if(data.getRawPointer is null || width == 0 || height == 0) { 1055 browserHandle.runOnWebView((wv) { 1056 wv.favicon = null; 1057 }); 1058 } else { 1059 auto s = data.get_size(); 1060 auto buffer = new ubyte[](s); 1061 auto got = data.get_data(buffer.ptr, buffer.length, 0); 1062 auto slice = buffer[0 .. got]; 1063 1064 auto img = new TrueColorImage (width, height, slice); 1065 1066 browserHandle.runOnWebView((wv) { 1067 wv.favicon = img; 1068 }); 1069 } 1070 } 1071 } 1072 1073 if(url.length) { 1074 auto callback = new Thing(browser); 1075 1076 browser.get_host().download_image(&str, true, 16, 0, callback.passable); 1077 } else { 1078 browser.runOnWebView((wv) { 1079 wv.favicon = null; 1080 }); 1081 } 1082 } 1083 1084 browser.runOnWebView((wv) { 1085 wv.favicon_url = url; 1086 }); 1087 } 1088 override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) { 1089 } 1090 override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) { 1091 return 0; 1092 } 1093 override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) { 1094 auto status = msg.toGC; 1095 browser.runOnWebView((wv) { 1096 wv.status = status; 1097 }); 1098 } 1099 override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) { 1100 // progress is from 0.0 to 1.0 1101 browser.runOnWebView((wv) { 1102 wv.loadingProgress = cast(int) (progress * 100); 1103 }); 1104 } 1105 override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) { 1106 return 1; // 1 means to suppress it being automatically output 1107 } 1108 override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) { 1109 return 0; 1110 } 1111 override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) { 1112 return 0; 1113 } 1114 override void on_media_access_change(RC!(cef_browser_t), int, int) { 1115 1116 } 1117 } 1118 1119 class MiniguiRequestHandler : CEF!cef_request_handler_t { 1120 override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow { 1121 return 0; 1122 } 1123 override int on_open_urlfrom_tab(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_string_utf16_t)*, cef_window_open_disposition_t, int) nothrow { 1124 return 0; 1125 } 1126 override cef_resource_request_handler_t* get_resource_request_handler(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int, const(cef_string_utf16_t)*, int*) nothrow { 1127 return null; 1128 } 1129 override int get_auth_credentials(RC!(cef_browser_t), const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, RC!(cef_auth_callback_t)) nothrow { 1130 // this is for http basic auth popup..... 1131 return 0; 1132 } 1133 override int on_certificate_error(RC!(cef_browser_t), cef_errorcode_t, const(cef_string_utf16_t)*, RC!(cef_sslinfo_t), RC!(cef_callback_t)) nothrow { 1134 return 0; 1135 } 1136 override int on_select_client_certificate(RC!(cef_browser_t), int, const(cef_string_utf16_t)*, int, ulong, cef_x509certificate_t**, RC!(cef_select_client_certificate_callback_t)) nothrow { 1137 return 0; 1138 } 1139 override void on_render_view_ready(RC!(cef_browser_t) p) nothrow { 1140 1141 } 1142 override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow { 1143 1144 } 1145 override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow { 1146 browser.runOnWebView(delegate(wv) { 1147 wv.executeJavascript("console.log('here');"); 1148 }); 1149 1150 } 1151 } 1152 1153 class MiniguiContextMenuHandler : CEF!cef_context_menu_handler_t { 1154 private MiniguiCefClient client; 1155 this(MiniguiCefClient client) { 1156 this.client = client; 1157 } 1158 1159 override void on_before_context_menu(RC!(cef_browser_t) browser, RC!(cef_frame_t) frame, RC!(cef_context_menu_params_t) params, RC!(cef_menu_model_t) model) nothrow { 1160 // FIXME: should i customize these? it is kinda specific to my browser 1161 int itemNo; 1162 1163 void addItem(string label, int commandNo) { 1164 auto lbl = cef_string_t(label); 1165 model.insert_item_at(/* index */ itemNo, /* command id */ cef_menu_id_t.MENU_ID_USER_FIRST + commandNo, &lbl); 1166 itemNo++; 1167 } 1168 1169 void addSeparator() { 1170 model.insert_separator_at(itemNo); 1171 itemNo++; 1172 } 1173 1174 auto flags = params.get_type_flags(); 1175 1176 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_LINK) { 1177 // cef_string_userfree_t linkUrl = params.get_unfiltered_link_url(); 1178 // toGCAndFree 1179 addItem("Open link in new window", 1); 1180 addItem("Copy link URL", 2); 1181 1182 // FIXME: open in other browsers 1183 addSeparator(); 1184 } 1185 1186 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_MEDIA) { 1187 // cef_string_userfree_t linkUrl = params.get_source_url(); 1188 // toGCAndFree 1189 addItem("Open media in new window", 3); 1190 addItem("Copy source URL", 4); 1191 addItem("Download media", 5); 1192 addSeparator(); 1193 } 1194 1195 1196 // get_page_url 1197 // get_title_text 1198 // has_image_contents ??? 1199 // get_source_url 1200 // get_xcoord and get_ycoord 1201 // get_selection_text 1202 1203 } 1204 override int run_context_menu(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_context_menu_params_t), RC!(cef_menu_model_t), RC!(cef_run_context_menu_callback_t)) nothrow { 1205 // could do a custom display here if i want but i think it is good enough as it is 1206 return 0; 1207 } 1208 override int on_context_menu_command(RC!(cef_browser_t) browser, RC!(cef_frame_t) frame, RC!(cef_context_menu_params_t) params, int commandId, cef_event_flags_t flags) nothrow { 1209 switch(commandId) { 1210 case cef_menu_id_t.MENU_ID_USER_FIRST + 1: // open link in new window 1211 auto what = params.get_unfiltered_link_url().toGCAndFree(); 1212 1213 browser.runOnWebView((widget) { 1214 auto event = new NewWindowRequestedEvent(what, widget); 1215 event.dispatch(); 1216 }); 1217 return 1; 1218 case cef_menu_id_t.MENU_ID_USER_FIRST + 2: // copy link url 1219 auto what = params.get_link_url().toGCAndFree(); 1220 1221 browser.runOnWebView((widget) { 1222 auto event = new CopyRequestedEvent(what, widget); 1223 event.dispatch(); 1224 }); 1225 return 1; 1226 case cef_menu_id_t.MENU_ID_USER_FIRST + 3: // open media in new window 1227 auto what = params.get_source_url().toGCAndFree(); 1228 1229 browser.runOnWebView((widget) { 1230 auto event = new NewWindowRequestedEvent(what, widget); 1231 event.dispatch(); 1232 }); 1233 return 1; 1234 case cef_menu_id_t.MENU_ID_USER_FIRST + 4: // copy source url 1235 auto what = params.get_source_url().toGCAndFree(); 1236 1237 browser.runOnWebView((widget) { 1238 auto event = new CopyRequestedEvent(what, widget); 1239 event.dispatch(); 1240 }); 1241 return 1; 1242 case cef_menu_id_t.MENU_ID_USER_FIRST + 5: // download media 1243 auto str = cef_string_t(params.get_source_url().toGCAndFree()); 1244 browser.get_host().start_download(&str); 1245 return 1; 1246 default: 1247 return 0; 1248 } 1249 } 1250 override void on_context_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1251 // to close the custom display 1252 } 1253 1254 override int run_quick_menu(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_point_t)*, const(cef_size_t)*, cef_quick_menu_edit_state_flags_t, RC!(cef_run_quick_menu_callback_t)) nothrow { 1255 return 0; 1256 } 1257 override int on_quick_menu_command(RC!(cef_browser_t), RC!(cef_frame_t), int, cef_event_flags_t) nothrow { 1258 return 0; 1259 } 1260 override void on_quick_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1261 1262 } 1263 } 1264 1265 class MiniguiFocusHandler : CEF!cef_focus_handler_t { 1266 override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow { 1267 browser.runOnWebView(delegate(wv) { 1268 Widget f; 1269 if(next) { 1270 f = Window.getFirstFocusable(wv.parentWindow); 1271 } else { 1272 foreach(w; &wv.parentWindow.focusableWidgets) { 1273 if(w is wv) 1274 break; 1275 f = w; 1276 } 1277 } 1278 if(f) 1279 f.focus(); 1280 }); 1281 } 1282 override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { 1283 /+ 1284 browser.runOnWebView((ev) { 1285 ev.focus(); // even this can steal focus from other parts of my application! 1286 }); 1287 +/ 1288 //sdpyPrintDebugString("setting"); 1289 1290 return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. 1291 // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent 1292 // even though things work fine if i always cancel except 1293 // it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh 1294 // it also breaks its own pop up menus and drop down boxes to allow this! wtf 1295 } 1296 override void on_got_focus(RC!(cef_browser_t) browser) nothrow { 1297 browser.runOnWebView((ev) { 1298 // this sometimes steals from the app too but it is relatively acceptable 1299 // steals when i mouse in from the side of the window quickly, but still 1300 // i want the minigui state to match so i'll allow it 1301 ev.focus(); 1302 }); 1303 } 1304 } 1305 1306 class MiniguiCefClient : CEF!cef_client_t { 1307 1308 void delegate(scope OpenNewWindowParams) openNewWindow; 1309 1310 MiniguiCefLifeSpanHandler lsh; 1311 MiniguiLoadHandler loadHandler; 1312 MiniguiDialogHandler dialogHandler; 1313 MiniguiDisplayHandler displayHandler; 1314 MiniguiDownloadHandler downloadHandler; 1315 MiniguiKeyboardHandler keyboardHandler; 1316 MiniguiFocusHandler focusHandler; 1317 MiniguiRequestHandler requestHandler; 1318 MiniguiContextMenuHandler contextMenuHandler; 1319 this(void delegate(scope OpenNewWindowParams) openNewWindow) { 1320 this.openNewWindow = openNewWindow; 1321 lsh = new MiniguiCefLifeSpanHandler(this); 1322 loadHandler = new MiniguiLoadHandler(); 1323 dialogHandler = new MiniguiDialogHandler(); 1324 displayHandler = new MiniguiDisplayHandler(); 1325 downloadHandler = new MiniguiDownloadHandler(); 1326 keyboardHandler = new MiniguiKeyboardHandler(); 1327 focusHandler = new MiniguiFocusHandler(); 1328 requestHandler = new MiniguiRequestHandler(); 1329 contextMenuHandler = new MiniguiContextMenuHandler(this); 1330 } 1331 1332 override cef_audio_handler_t* get_audio_handler() { 1333 return null; 1334 } 1335 override cef_context_menu_handler_t* get_context_menu_handler() { 1336 return contextMenuHandler.returnable; 1337 } 1338 override cef_dialog_handler_t* get_dialog_handler() { 1339 return dialogHandler.returnable; 1340 } 1341 override cef_display_handler_t* get_display_handler() { 1342 return displayHandler.returnable; 1343 } 1344 override cef_download_handler_t* get_download_handler() { 1345 return downloadHandler.returnable; 1346 } 1347 override cef_drag_handler_t* get_drag_handler() { 1348 return null; 1349 } 1350 override cef_find_handler_t* get_find_handler() { 1351 return null; 1352 } 1353 override cef_focus_handler_t* get_focus_handler() { 1354 return focusHandler.returnable; 1355 } 1356 override cef_jsdialog_handler_t* get_jsdialog_handler() { 1357 // needed for alert etc. 1358 return null; 1359 } 1360 override cef_keyboard_handler_t* get_keyboard_handler() { 1361 // this can handle keyboard shortcuts etc 1362 return keyboardHandler.returnable; 1363 } 1364 override cef_life_span_handler_t* get_life_span_handler() { 1365 return lsh.returnable; 1366 } 1367 override cef_load_handler_t* get_load_handler() { 1368 return loadHandler.returnable; 1369 } 1370 override cef_render_handler_t* get_render_handler() { 1371 // this thing might work for an off-screen thing 1372 // like to an image or to a video stream maybe 1373 // 1374 // might be useful to have it render here then send it over too for remote X sharing a process 1375 return null; 1376 } 1377 override cef_request_handler_t* get_request_handler() { 1378 return requestHandler.returnable; 1379 } 1380 override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) { 1381 return 0; // return 1 if you can actually handle the message 1382 } 1383 override cef_frame_handler_t* get_frame_handler() nothrow { 1384 return null; 1385 } 1386 override cef_print_handler_t* get_print_handler() nothrow { 1387 return null; 1388 } 1389 1390 override cef_command_handler_t* get_command_handler() { 1391 return null; 1392 } 1393 1394 override cef_permission_handler_t* get_permission_handler() { 1395 return null; 1396 } 1397 1398 } 1399 } 1400 1401 class BrowserClosedEvent : Event { 1402 enum EventString = "browserclosed"; 1403 1404 this(Widget target) { super(EventString, target); } 1405 override bool cancelable() const { return false; } 1406 } 1407 1408 class CopyRequestedEvent : Event { 1409 enum EventString = "browsercopyrequested"; 1410 1411 string what; 1412 1413 this(string what, Widget target) { this.what = what; super(EventString, target); } 1414 override bool cancelable() const { return false; } 1415 } 1416 1417 class NewWindowRequestedEvent : Event { 1418 enum EventString = "browserwindowrequested"; 1419 1420 string url; 1421 1422 this(string url, Widget target) { this.url = url; super(EventString, target); } 1423 override bool cancelable() const { return false; } 1424 } 1425 1426 1427 1428 /+ 1429 pragma(mangle, "_ZN12CefWindowX115FocusEv") 1430 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE") 1431 extern(C++) 1432 export void _ZN12CefWindowX115FocusEv() { 1433 sdpyPrintDebugString("OVERRIDDEN"); 1434 } 1435 +/