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 void on_before_dev_tools_popup(RC!(cef_browser_t), cef_window_info_t*, cef_client_t**, cef_browser_settings_t*, cef_dictionary_value_t**, int*) nothrow { 768 769 } 770 771 override int on_before_popup( 772 RC!cef_browser_t browser, 773 RC!cef_frame_t frame, 774 const(cef_string_t)* target_url, 775 const(cef_string_t)* target_frame_name, 776 cef_window_open_disposition_t target_disposition, 777 int user_gesture, 778 const(cef_popup_features_t)* popupFeatures, 779 cef_window_info_t* windowInfo, 780 cef_client_t** client, 781 cef_browser_settings_t* browser_settings, 782 cef_dictionary_value_t** extra_info, 783 int* no_javascript_access 784 ) { 785 sdpyPrintDebugString("on_before_popup"); 786 if(this.client.openNewWindow is null) 787 return 1; // new windows disabled 788 789 try { 790 int ret; 791 792 import core.thread; 793 try { thread_attachThis(); } catch(Exception e) {} 794 795 // FIXME: change settings here 796 797 runInGuiThread({ 798 ret = 1; 799 scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { 800 ret = 0; 801 if(parent !is null) { 802 auto widget = new WebViewWidget_CEF(this.client, parent, false); 803 (*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle; 804 805 passed_settings.set(browser_settings); 806 807 return widget; 808 } 809 return null; 810 }; 811 this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept)); 812 return; 813 }); 814 815 return ret; 816 } catch(Exception e) { 817 return 1; 818 } 819 /+ 820 if(!user_gesture) 821 return 1; // if not created by the user, cancel it; basic popup blocking 822 +/ 823 } 824 override void on_after_created(RC!cef_browser_t browser) { 825 auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle(); 826 auto ptr = browser.passable; // this adds to the refcount until it gets inside 827 828 import core.thread; 829 try { thread_attachThis(); } catch(Exception e) {} 830 831 // the only reliable key (at least as far as i can tell) is the window handle 832 // so gonna look that up and do the sync mapping that way. 833 runInGuiThreadAsync({ 834 version(Windows) { 835 auto parent = GetParent(handle); 836 } else static if(UsingSimpledisplayX11) { 837 import arsd.simpledisplay : Window; 838 Window root; 839 Window parent; 840 Window ozone; 841 uint c = 0; 842 auto display = XDisplayConnection.get; 843 Window* children; 844 XQueryTree(display, handle, &root, &parent, &children, &c); 845 if(c == 1) 846 ozone = children[0]; 847 XFree(children); 848 } else static assert(0); 849 850 if(auto wvp = parent in WebViewWidget.mapping) { 851 auto wv = *wvp; 852 wv.browserWindow = handle; 853 wv.browserHandle = RC!cef_browser_t(ptr); 854 wv.ozone = ozone ? ozone : handle; 855 856 wv.browserHostWrapped = new SimpleWindow(handle); 857 // XSelectInput(XDisplayConnection.get, handle, EventMask.StructureNotifyMask); 858 859 wv.browserHostWrapped.onDestroyed = delegate{ 860 import std.stdio; writefln("browser host %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 861 862 auto bce = new BrowserClosedEvent(wv); 863 bce.dispatch(); 864 }; 865 866 // need this to forward key events to 867 wv.browserWindowWrapped = new SimpleWindow(wv.ozone); 868 869 /+ 870 XSelectInput(XDisplayConnection.get, wv.ozone, EventMask.StructureNotifyMask); 871 wv.browserWindowWrapped.onDestroyed = delegate{ 872 import std.stdio; writefln("browser core %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 873 874 //auto bce = new BrowserClosedEvent(wv); 875 //bce.dispatch(); 876 }; 877 +/ 878 879 /+ 880 XSelectInput(XDisplayConnection.get, ozone, EventMask.FocusChangeMask); 881 wv.browserWindowWrapped.onFocusChange = (bool got) { 882 import std.format; 883 sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window)); 884 }; 885 +/ 886 887 wv.registerMovementAdditionalWork(); 888 889 WebViewWidget.browserMapping[handle] = wv; 890 } else assert(0); 891 }); 892 } 893 override int do_close(RC!cef_browser_t browser) { 894 import std.stdio; 895 debug writeln("do_close"); 896 /+ 897 browser.runOnWebView((wv) { 898 wv.browserWindowWrapped.close(); 899 .destroy(wv.browserHandle); 900 }); 901 902 return 1; 903 +/ 904 905 return 0; 906 } 907 override void on_before_close(RC!cef_browser_t browser) { 908 import std.stdio; debug writeln("notify"); 909 browser.runOnWebView((wv) { 910 .destroy(wv.browserHandle); 911 }); 912 /+ 913 try 914 semaphore.notify; 915 catch(Exception e) { assert(0); } 916 +/ 917 } 918 } 919 920 class MiniguiLoadHandler : CEF!cef_load_handler_t { 921 override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) { 922 /+ 923 browser.runOnWebView((WebViewWidget wvw) { 924 wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree; 925 }); 926 +/ 927 } 928 override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) { 929 } 930 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)*) { 931 } 932 override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) { 933 } 934 } 935 936 class MiniguiDialogHandler : CEF!cef_dialog_handler_t { 937 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, 938 cef_string_list_t accept_filters, 939 cef_string_list_t accept_extensions, 940 cef_string_list_t accept_descriptions, 941 RC!(cef_file_dialog_callback_t) callback) 942 { 943 try { 944 auto ptr = callback.passable(); 945 browser.runOnWebView((wv) { 946 getOpenFileName(wv.parentWindow, (string name) { 947 auto callback = RC!cef_file_dialog_callback_t(ptr); 948 auto list = libcef.string_list_alloc(); 949 auto item = cef_string_t(name); 950 libcef.string_list_append(list, &item); 951 callback.cont(list); 952 }, null, null, () { 953 auto callback = RC!cef_file_dialog_callback_t(ptr); 954 callback.cancel(); 955 }, "/home/me/"); 956 }); 957 } catch(Exception e) {} 958 959 return 1; 960 } 961 } 962 963 class MiniguiDownloadHandler : CEF!cef_download_handler_t { 964 override int on_before_download( 965 RC!cef_browser_t browser, 966 RC!cef_download_item_t download_item, 967 const(cef_string_t)* suggested_name, 968 RC!cef_before_download_callback_t callback 969 ) nothrow 970 { 971 // FIXME: different filename and check if exists for overwrite etc 972 auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length])); 973 sdpyPrintDebugString(fn.toGC); 974 callback.cont(&fn, false); 975 976 return 1; 977 } 978 979 override void on_download_updated( 980 RC!cef_browser_t browser, 981 RC!cef_download_item_t download_item, 982 RC!cef_download_item_callback_t cancel 983 ) nothrow 984 { 985 sdpyPrintDebugString(download_item.get_percent_complete()); 986 // FIXME 987 } 988 989 override int can_download(RC!(cef_browser_t), const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) { 990 return 1; 991 } 992 } 993 994 class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t { 995 override int on_pre_key_event( 996 RC!(cef_browser_t) browser, 997 const(cef_key_event_t)* event, 998 XEvent* osEvent, 999 int* isShortcut 1000 ) nothrow { 1001 /+ 1002 sdpyPrintDebugString("---pre---"); 1003 sdpyPrintDebugString(event.focus_on_editable_field); 1004 sdpyPrintDebugString(event.windows_key_code); 1005 sdpyPrintDebugString(event.modifiers); 1006 sdpyPrintDebugString(event.unmodified_character); 1007 +/ 1008 //*isShortcut = 1; 1009 return 0; // 1 if handled, which cancels sending it to browser 1010 } 1011 1012 override int on_key_event( 1013 RC!(cef_browser_t) browser, 1014 const(cef_key_event_t)* event, 1015 XEvent* osEvent 1016 ) nothrow { 1017 /+ 1018 sdpyPrintDebugString("---key---"); 1019 sdpyPrintDebugString(event.focus_on_editable_field); 1020 sdpyPrintDebugString(event.windows_key_code); 1021 sdpyPrintDebugString(event.modifiers); 1022 +/ 1023 return 0; // 1 if handled 1024 } 1025 } 1026 1027 class MiniguiDisplayHandler : CEF!cef_display_handler_t { 1028 override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) { 1029 auto url = address.toGC; 1030 browser.runOnWebView((wv) { 1031 wv.url = url; 1032 }); 1033 } 1034 override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) { 1035 auto t = title.toGC; 1036 browser.runOnWebView((wv) { 1037 wv.title = t; 1038 }); 1039 } 1040 override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) { 1041 string url; 1042 auto size = libcef.string_list_size(urls); 1043 if(size > 0) { 1044 cef_string_t str; 1045 libcef.string_list_value(urls, 0, &str); 1046 url = str.toGC; 1047 1048 static class Thing : CEF!cef_download_image_callback_t { 1049 RC!cef_browser_t browserHandle; 1050 this(RC!cef_browser_t browser) nothrow { 1051 this.browserHandle = browser; 1052 } 1053 override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow { 1054 int width; 1055 int height; 1056 if(image.getRawPointer is null) { 1057 browserHandle.runOnWebView((wv) { 1058 wv.favicon = null; 1059 }); 1060 return; 1061 } 1062 1063 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); 1064 1065 if(data.getRawPointer is null || width == 0 || height == 0) { 1066 browserHandle.runOnWebView((wv) { 1067 wv.favicon = null; 1068 }); 1069 } else { 1070 auto s = data.get_size(); 1071 auto buffer = new ubyte[](s); 1072 auto got = data.get_data(buffer.ptr, buffer.length, 0); 1073 auto slice = buffer[0 .. got]; 1074 1075 auto img = new TrueColorImage (width, height, slice); 1076 1077 browserHandle.runOnWebView((wv) { 1078 wv.favicon = img; 1079 }); 1080 } 1081 } 1082 } 1083 1084 if(url.length) { 1085 auto callback = new Thing(browser); 1086 1087 browser.get_host().download_image(&str, true, 16, 0, callback.passable); 1088 } else { 1089 browser.runOnWebView((wv) { 1090 wv.favicon = null; 1091 }); 1092 } 1093 } 1094 1095 browser.runOnWebView((wv) { 1096 wv.favicon_url = url; 1097 }); 1098 } 1099 override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) { 1100 } 1101 override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) { 1102 return 0; 1103 } 1104 override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) { 1105 auto status = msg.toGC; 1106 browser.runOnWebView((wv) { 1107 wv.status = status; 1108 }); 1109 } 1110 override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) { 1111 // progress is from 0.0 to 1.0 1112 browser.runOnWebView((wv) { 1113 wv.loadingProgress = cast(int) (progress * 100); 1114 }); 1115 } 1116 override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) { 1117 return 1; // 1 means to suppress it being automatically output 1118 } 1119 override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) { 1120 return 0; 1121 } 1122 override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) { 1123 return 0; 1124 } 1125 override void on_media_access_change(RC!(cef_browser_t), int, int) { 1126 1127 } 1128 } 1129 1130 class MiniguiRequestHandler : CEF!cef_request_handler_t { 1131 1132 override int on_render_process_unresponsive(RC!(cef_browser_t), RC!(cef_unresponsive_process_callback_t)) nothrow { 1133 return 0; 1134 } 1135 override void on_render_process_responsive(RC!(cef_browser_t) p) nothrow { 1136 1137 } 1138 1139 override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow { 1140 return 0; 1141 } 1142 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 { 1143 return 0; 1144 } 1145 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 { 1146 return null; 1147 } 1148 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 { 1149 // this is for http basic auth popup..... 1150 return 0; 1151 } 1152 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 { 1153 return 0; 1154 } 1155 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 { 1156 return 0; 1157 } 1158 override void on_render_view_ready(RC!(cef_browser_t) p) nothrow { 1159 1160 } 1161 override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t, int error_code, const(cef_string_utf16_t)*) nothrow { 1162 1163 } 1164 override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow { 1165 browser.runOnWebView(delegate(wv) { 1166 wv.executeJavascript("console.log('here');"); 1167 }); 1168 1169 } 1170 } 1171 1172 class MiniguiContextMenuHandler : CEF!cef_context_menu_handler_t { 1173 private MiniguiCefClient client; 1174 this(MiniguiCefClient client) { 1175 this.client = client; 1176 } 1177 1178 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 { 1179 // FIXME: should i customize these? it is kinda specific to my browser 1180 int itemNo; 1181 1182 void addItem(string label, int commandNo) { 1183 auto lbl = cef_string_t(label); 1184 model.insert_item_at(/* index */ itemNo, /* command id */ cef_menu_id_t.MENU_ID_USER_FIRST + commandNo, &lbl); 1185 itemNo++; 1186 } 1187 1188 void addSeparator() { 1189 model.insert_separator_at(itemNo); 1190 itemNo++; 1191 } 1192 1193 auto flags = params.get_type_flags(); 1194 1195 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_LINK) { 1196 // cef_string_userfree_t linkUrl = params.get_unfiltered_link_url(); 1197 // toGCAndFree 1198 addItem("Open link in new window", 1); 1199 addItem("Copy link URL", 2); 1200 1201 // FIXME: open in other browsers 1202 // FIXME: open in ytv 1203 addSeparator(); 1204 } 1205 1206 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_MEDIA) { 1207 // cef_string_userfree_t linkUrl = params.get_source_url(); 1208 // toGCAndFree 1209 addItem("Open media in new window", 3); 1210 addItem("Copy source URL", 4); 1211 addItem("Download media", 5); 1212 addSeparator(); 1213 } 1214 1215 1216 // get_page_url 1217 // get_title_text 1218 // has_image_contents ??? 1219 // get_source_url 1220 // get_xcoord and get_ycoord 1221 // get_selection_text 1222 1223 } 1224 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 { 1225 // could do a custom display here if i want but i think it is good enough as it is 1226 return 0; 1227 } 1228 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 { 1229 switch(commandId) { 1230 case cef_menu_id_t.MENU_ID_USER_FIRST + 1: // open link in new window 1231 auto what = params.get_unfiltered_link_url().toGCAndFree(); 1232 1233 browser.runOnWebView((widget) { 1234 auto event = new NewWindowRequestedEvent(what, widget); 1235 event.dispatch(); 1236 }); 1237 return 1; 1238 case cef_menu_id_t.MENU_ID_USER_FIRST + 2: // copy link url 1239 auto what = params.get_link_url().toGCAndFree(); 1240 1241 browser.runOnWebView((widget) { 1242 auto event = new CopyRequestedEvent(what, widget); 1243 event.dispatch(); 1244 }); 1245 return 1; 1246 case cef_menu_id_t.MENU_ID_USER_FIRST + 3: // open media in new window 1247 auto what = params.get_source_url().toGCAndFree(); 1248 1249 browser.runOnWebView((widget) { 1250 auto event = new NewWindowRequestedEvent(what, widget); 1251 event.dispatch(); 1252 }); 1253 return 1; 1254 case cef_menu_id_t.MENU_ID_USER_FIRST + 4: // copy source url 1255 auto what = params.get_source_url().toGCAndFree(); 1256 1257 browser.runOnWebView((widget) { 1258 auto event = new CopyRequestedEvent(what, widget); 1259 event.dispatch(); 1260 }); 1261 return 1; 1262 case cef_menu_id_t.MENU_ID_USER_FIRST + 5: // download media 1263 auto str = cef_string_t(params.get_source_url().toGCAndFree()); 1264 browser.get_host().start_download(&str); 1265 return 1; 1266 default: 1267 return 0; 1268 } 1269 } 1270 override void on_context_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1271 // to close the custom display 1272 } 1273 1274 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 { 1275 return 0; 1276 } 1277 override int on_quick_menu_command(RC!(cef_browser_t), RC!(cef_frame_t), int, cef_event_flags_t) nothrow { 1278 return 0; 1279 } 1280 override void on_quick_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1281 1282 } 1283 } 1284 1285 class MiniguiFocusHandler : CEF!cef_focus_handler_t { 1286 override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow { 1287 browser.runOnWebView(delegate(wv) { 1288 Widget f; 1289 if(next) { 1290 f = Window.getFirstFocusable(wv.parentWindow); 1291 } else { 1292 foreach(w; &wv.parentWindow.focusableWidgets) { 1293 if(w is wv) 1294 break; 1295 f = w; 1296 } 1297 } 1298 if(f) 1299 f.focus(); 1300 }); 1301 } 1302 override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { 1303 /+ 1304 browser.runOnWebView((ev) { 1305 ev.focus(); // even this can steal focus from other parts of my application! 1306 }); 1307 +/ 1308 //sdpyPrintDebugString("setting"); 1309 1310 return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. 1311 // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent 1312 // even though things work fine if i always cancel except 1313 // it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh 1314 // it also breaks its own pop up menus and drop down boxes to allow this! wtf 1315 } 1316 override void on_got_focus(RC!(cef_browser_t) browser) nothrow { 1317 browser.runOnWebView((ev) { 1318 // this sometimes steals from the app too but it is relatively acceptable 1319 // steals when i mouse in from the side of the window quickly, but still 1320 // i want the minigui state to match so i'll allow it 1321 ev.focus(); 1322 }); 1323 } 1324 } 1325 1326 class MiniguiCefClient : CEF!cef_client_t { 1327 1328 void delegate(scope OpenNewWindowParams) openNewWindow; 1329 1330 MiniguiCefLifeSpanHandler lsh; 1331 MiniguiLoadHandler loadHandler; 1332 MiniguiDialogHandler dialogHandler; 1333 MiniguiDisplayHandler displayHandler; 1334 MiniguiDownloadHandler downloadHandler; 1335 MiniguiKeyboardHandler keyboardHandler; 1336 MiniguiFocusHandler focusHandler; 1337 MiniguiRequestHandler requestHandler; 1338 MiniguiContextMenuHandler contextMenuHandler; 1339 this(void delegate(scope OpenNewWindowParams) openNewWindow) { 1340 this.openNewWindow = openNewWindow; 1341 lsh = new MiniguiCefLifeSpanHandler(this); 1342 loadHandler = new MiniguiLoadHandler(); 1343 dialogHandler = new MiniguiDialogHandler(); 1344 displayHandler = new MiniguiDisplayHandler(); 1345 downloadHandler = new MiniguiDownloadHandler(); 1346 keyboardHandler = new MiniguiKeyboardHandler(); 1347 focusHandler = new MiniguiFocusHandler(); 1348 requestHandler = new MiniguiRequestHandler(); 1349 contextMenuHandler = new MiniguiContextMenuHandler(this); 1350 } 1351 1352 override cef_audio_handler_t* get_audio_handler() { 1353 return null; 1354 } 1355 override cef_context_menu_handler_t* get_context_menu_handler() { 1356 return contextMenuHandler.returnable; 1357 } 1358 override cef_dialog_handler_t* get_dialog_handler() { 1359 return dialogHandler.returnable; 1360 } 1361 override cef_display_handler_t* get_display_handler() { 1362 return displayHandler.returnable; 1363 } 1364 override cef_download_handler_t* get_download_handler() { 1365 return downloadHandler.returnable; 1366 } 1367 override cef_drag_handler_t* get_drag_handler() { 1368 return null; 1369 } 1370 override cef_find_handler_t* get_find_handler() { 1371 return null; 1372 } 1373 override cef_focus_handler_t* get_focus_handler() { 1374 return focusHandler.returnable; 1375 } 1376 override cef_jsdialog_handler_t* get_jsdialog_handler() { 1377 // needed for alert etc. 1378 return null; 1379 } 1380 override cef_keyboard_handler_t* get_keyboard_handler() { 1381 // this can handle keyboard shortcuts etc 1382 return keyboardHandler.returnable; 1383 } 1384 override cef_life_span_handler_t* get_life_span_handler() { 1385 return lsh.returnable; 1386 } 1387 override cef_load_handler_t* get_load_handler() { 1388 return loadHandler.returnable; 1389 } 1390 override cef_render_handler_t* get_render_handler() { 1391 // this thing might work for an off-screen thing 1392 // like to an image or to a video stream maybe 1393 // 1394 // might be useful to have it render here then send it over too for remote X sharing a process 1395 return null; 1396 } 1397 override cef_request_handler_t* get_request_handler() { 1398 return requestHandler.returnable; 1399 } 1400 override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) { 1401 return 0; // return 1 if you can actually handle the message 1402 } 1403 override cef_frame_handler_t* get_frame_handler() nothrow { 1404 return null; 1405 } 1406 override cef_print_handler_t* get_print_handler() nothrow { 1407 return null; 1408 } 1409 1410 override cef_command_handler_t* get_command_handler() { 1411 return null; 1412 } 1413 1414 override cef_permission_handler_t* get_permission_handler() { 1415 return null; 1416 } 1417 1418 } 1419 } 1420 1421 class BrowserClosedEvent : Event { 1422 enum EventString = "browserclosed"; 1423 1424 this(Widget target) { super(EventString, target); } 1425 override bool cancelable() const { return false; } 1426 } 1427 1428 class CopyRequestedEvent : Event { 1429 enum EventString = "browsercopyrequested"; 1430 1431 string what; 1432 1433 this(string what, Widget target) { this.what = what; super(EventString, target); } 1434 override bool cancelable() const { return false; } 1435 } 1436 1437 class NewWindowRequestedEvent : Event { 1438 enum EventString = "browserwindowrequested"; 1439 1440 string url; 1441 1442 this(string url, Widget target) { this.url = url; super(EventString, target); } 1443 override bool cancelable() const { return false; } 1444 } 1445 1446 1447 1448 /+ 1449 pragma(mangle, "_ZN12CefWindowX115FocusEv") 1450 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE") 1451 extern(C++) 1452 export void _ZN12CefWindowX115FocusEv() { 1453 sdpyPrintDebugString("OVERRIDDEN"); 1454 } 1455 +/