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 // sdpyPrintDebugString("Sending FocusIn"); 547 548 trapXErrors( { 549 XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); 550 }); 551 552 // this also works if the message is buggy and it avoids weirdness from raising window etc 553 //executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));"); 554 }); 555 this.addEventListener((BlurEvent be) { 556 if(!browserHandle) return; 557 558 XFocusChangeEvent ev; 559 ev.type = arsd.simpledisplay.EventType.FocusOut; 560 ev.display = XDisplayConnection.get; 561 ev.window = ozone; 562 ev.mode = NotifyModes.NotifyNormal; 563 ev.detail = NotifyDetail.NotifyNonlinearVirtual; 564 565 // sdpyPrintDebugString("Sending FocusOut"); 566 567 trapXErrors( { 568 XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev); 569 }); 570 571 //executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));"); 572 }); 573 574 bool closeAttempted = false; 575 576 if(isDevTools) 577 this.parentWindow.addEventListener((scope ClosingEvent ce) { 578 this.parentWindow.hide(); 579 ce.preventDefault(); 580 }); 581 else 582 this.parentWindow.addEventListener((scope ClosingEvent ce) { 583 if(devTools) 584 devTools.close(); 585 if(browserHandle) { 586 if(!closeAttempted) { 587 closeAttempted = true; 588 browserHandle.get_host.close_browser(false); 589 ce.preventDefault(); 590 sdpyPrintDebugString("closing 1"); 591 } else { 592 browserHandle.get_host.close_browser(true); 593 sdpyPrintDebugString("closing 2"); 594 } 595 } 596 }); 597 } 598 599 ~this() { 600 import core.stdc.stdio; 601 printf("GC'd %p\n", cast(void*) this); 602 } 603 604 private MiniguiCefClient client; 605 606 override void registerMovementAdditionalWork() { 607 if(browserWindow) { 608 // import std.stdio; writeln("new size ", width, "x", height); 609 static if(UsingSimpledisplayX11) { 610 XResizeWindow(XDisplayConnection.get, browserWindow, width, height); 611 if(ozone) XResizeWindow(XDisplayConnection.get, ozone, width, height); 612 } 613 // FIXME: do for Windows too 614 } 615 } 616 617 SimpleWindow browserHostWrapped; 618 SimpleWindow browserWindowWrapped; 619 override SimpleWindow focusableWindow() { 620 if(browserWindowWrapped is null && browserWindow) { 621 browserWindowWrapped = new SimpleWindow(browserWindow); 622 // FIXME: this should never actually happen should it 623 } 624 return browserWindowWrapped; 625 } 626 627 private NativeWindowHandle browserWindow; 628 private NativeWindowHandle ozone; 629 private RC!cef_browser_t browserHandle; 630 631 private static WebViewWidget[NativeWindowHandle] mapping; 632 private static WebViewWidget[NativeWindowHandle] browserMapping; 633 634 private { 635 string findingText; 636 bool findingCase; 637 } 638 639 // might not be stable, webview does this fully integrated 640 void findText(string text, bool forward = true, bool matchCase = false, bool findNext = false) { 641 if(browserHandle) { 642 auto host = browserHandle.get_host(); 643 644 auto txt = cef_string_t(text); 645 host.find(&txt, forward, matchCase, findNext); 646 647 findingText = text; 648 findingCase = matchCase; 649 } 650 } 651 652 // ditto 653 void findPrevious() { 654 if(!browserHandle) 655 return; 656 auto host = browserHandle.get_host(); 657 auto txt = cef_string_t(findingText); 658 host.find(&txt, 0, findingCase, 1); 659 } 660 661 // ditto 662 void findNext() { 663 if(!browserHandle) 664 return; 665 auto host = browserHandle.get_host(); 666 auto txt = cef_string_t(findingText); 667 host.find(&txt, 1, findingCase, 1); 668 } 669 670 // ditto 671 void stopFind() { 672 if(!browserHandle) 673 return; 674 auto host = browserHandle.get_host(); 675 host.stop_finding(1); 676 } 677 678 override void refresh() { if(browserHandle) browserHandle.reload(); } 679 override void back() { if(browserHandle) browserHandle.go_back(); } 680 override void forward() { if(browserHandle) browserHandle.go_forward(); } 681 override void stop() { if(browserHandle) browserHandle.stop_load(); } 682 683 override void navigate(string url) { 684 if(!browserHandle) return; 685 auto s = cef_string_t(url); 686 browserHandle.get_main_frame.load_url(&s); 687 } 688 689 // the url and line are for error reporting purposes 690 override void executeJavascript(string code, string url = null, int line = 0) { 691 if(!browserHandle) return; 692 693 auto c = cef_string_t(code); 694 auto u = cef_string_t(url); 695 browserHandle.get_main_frame.execute_java_script(&c, &u, line); 696 } 697 698 private Window devTools; 699 override void showDevTools() { 700 if(!browserHandle) return; 701 702 if(devTools is null) { 703 auto host = browserHandle.get_host; 704 705 if(host.has_dev_tools()) { 706 host.close_dev_tools(); 707 return; 708 } 709 710 cef_window_info_t windowinfo; 711 version(linux) { 712 auto sw = new Window("DevTools"); 713 //sw.win.beingOpenKeepsAppOpen = false; 714 devTools = sw; 715 716 auto wv = new WebViewWidget_CEF(client, sw, true); 717 718 sw.show(); 719 720 windowinfo.parent_window = wv.containerWindow.nativeWindowHandle; 721 } 722 host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */); 723 } else { 724 if(devTools.hidden) 725 devTools.show(); 726 else 727 devTools.hide(); 728 } 729 } 730 731 // FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries. 732 733 // JS on init? 734 // JS bindings? 735 // user styles? 736 // navigate to string? (can just use a data uri maybe?) 737 // custom scheme handlers? 738 739 // navigation callbacks to prohibit certain things or move links to new window etc? 740 } 741 742 version(cef) { 743 744 //import core.sync.semaphore; 745 //__gshared Semaphore semaphore; 746 747 /+ 748 Finds the WebViewWidget associated with the given browser, then runs the given code in the gui thread on it. 749 +/ 750 void runOnWebView(RC!cef_browser_t browser, void delegate(WebViewWidget) dg) nothrow { 751 auto wh = cast(NativeWindowHandle) browser.get_host.get_window_handle; 752 753 import core.thread; 754 try { thread_attachThis(); } catch(Exception e) {} 755 756 runInGuiThreadAsync({ 757 if(auto wvp = wh in WebViewWidget.browserMapping) { 758 dg(*wvp); 759 } else { 760 writeln("not found ", wh, WebViewWidget.browserMapping); 761 } 762 }); 763 } 764 765 class MiniguiCefLifeSpanHandler : CEF!cef_life_span_handler_t { 766 private MiniguiCefClient client; 767 this(MiniguiCefClient client) { 768 this.client = client; 769 } 770 771 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 { 772 773 } 774 775 override int on_before_popup( 776 RC!cef_browser_t browser, 777 RC!cef_frame_t frame, 778 const(cef_string_t)* target_url, 779 const(cef_string_t)* target_frame_name, 780 cef_window_open_disposition_t target_disposition, 781 int user_gesture, 782 const(cef_popup_features_t)* popupFeatures, 783 cef_window_info_t* windowInfo, 784 cef_client_t** client, 785 cef_browser_settings_t* browser_settings, 786 cef_dictionary_value_t** extra_info, 787 int* no_javascript_access 788 ) { 789 sdpyPrintDebugString("on_before_popup"); 790 if(this.client.openNewWindow is null) 791 return 1; // new windows disabled 792 793 try { 794 int ret; 795 796 import core.thread; 797 try { thread_attachThis(); } catch(Exception e) {} 798 799 // FIXME: change settings here 800 801 runInGuiThread({ 802 ret = 1; 803 scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) { 804 ret = 0; 805 if(parent !is null) { 806 auto widget = new WebViewWidget_CEF(this.client, parent, false); 807 (*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle; 808 809 passed_settings.set(browser_settings); 810 811 return widget; 812 } 813 return null; 814 }; 815 this.client.openNewWindow(OpenNewWindowParams(target_url.toGC, accept)); 816 return; 817 }); 818 819 return ret; 820 } catch(Exception e) { 821 return 1; 822 } 823 /+ 824 if(!user_gesture) 825 return 1; // if not created by the user, cancel it; basic popup blocking 826 +/ 827 } 828 override void on_after_created(RC!cef_browser_t browser) { 829 auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle(); 830 auto ptr = browser.passable; // this adds to the refcount until it gets inside 831 832 import core.thread; 833 try { thread_attachThis(); } catch(Exception e) {} 834 835 // the only reliable key (at least as far as i can tell) is the window handle 836 // so gonna look that up and do the sync mapping that way. 837 runInGuiThreadAsync({ 838 version(Windows) { 839 auto parent = GetParent(handle); 840 } else static if(UsingSimpledisplayX11) { 841 import arsd.simpledisplay : Window; 842 Window root; 843 Window parent; 844 Window ozone; 845 uint c = 0; 846 auto display = XDisplayConnection.get; 847 Window* children; 848 XQueryTree(display, handle, &root, &parent, &children, &c); 849 if(c == 1) 850 ozone = children[0]; 851 XFree(children); 852 } else static assert(0); 853 854 if(auto wvp = parent in WebViewWidget.mapping) { 855 auto wv = *wvp; 856 wv.browserWindow = handle; 857 wv.browserHandle = RC!cef_browser_t(ptr); 858 wv.ozone = ozone ? ozone : handle; 859 860 wv.browserHostWrapped = new SimpleWindow(handle); 861 // XSelectInput(XDisplayConnection.get, handle, EventMask.StructureNotifyMask); 862 863 wv.browserHostWrapped.onDestroyed = delegate{ 864 import std.stdio; writefln("browser host %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 865 866 auto bce = new BrowserClosedEvent(wv); 867 bce.dispatch(); 868 }; 869 870 // need this to forward key events to 871 wv.browserWindowWrapped = new SimpleWindow(wv.ozone); 872 873 /+ 874 XSelectInput(XDisplayConnection.get, wv.ozone, EventMask.StructureNotifyMask); 875 wv.browserWindowWrapped.onDestroyed = delegate{ 876 import std.stdio; writefln("browser core %d destroyed (handle %d)", wv.browserWindowWrapped.window, wv.browserWindow); 877 878 //auto bce = new BrowserClosedEvent(wv); 879 //bce.dispatch(); 880 }; 881 +/ 882 883 /+ 884 XSelectInput(XDisplayConnection.get, ozone, EventMask.FocusChangeMask); 885 wv.browserWindowWrapped.onFocusChange = (bool got) { 886 import std.format; 887 sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window)); 888 }; 889 +/ 890 891 wv.registerMovementAdditionalWork(); 892 893 WebViewWidget.browserMapping[handle] = wv; 894 } else assert(0); 895 }); 896 } 897 override int do_close(RC!cef_browser_t browser) { 898 import std.stdio; 899 debug writeln("do_close"); 900 /+ 901 browser.runOnWebView((wv) { 902 wv.browserWindowWrapped.close(); 903 .destroy(wv.browserHandle); 904 }); 905 906 return 1; 907 +/ 908 909 return 0; 910 } 911 override void on_before_close(RC!cef_browser_t browser) { 912 import std.stdio; debug writeln("notify"); 913 browser.runOnWebView((wv) { 914 .destroy(wv.browserHandle); 915 }); 916 /+ 917 try 918 semaphore.notify; 919 catch(Exception e) { assert(0); } 920 +/ 921 } 922 } 923 924 class MiniguiLoadHandler : CEF!cef_load_handler_t { 925 override void on_loading_state_change(RC!(cef_browser_t) browser, int isLoading, int canGoBack, int canGoForward) { 926 /+ 927 browser.runOnWebView((WebViewWidget wvw) { 928 wvw.parentWindow.win.title = wvw.browserHandle.get_main_frame.get_url.toGCAndFree; 929 }); 930 +/ 931 } 932 override void on_load_start(RC!(cef_browser_t), RC!(cef_frame_t), cef_transition_type_t) { 933 } 934 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)*) { 935 } 936 override void on_load_end(RC!(cef_browser_t), RC!(cef_frame_t), int) { 937 } 938 } 939 940 class MiniguiDialogHandler : CEF!cef_dialog_handler_t { 941 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, 942 cef_string_list_t accept_filters, 943 cef_string_list_t accept_extensions, 944 cef_string_list_t accept_descriptions, 945 RC!(cef_file_dialog_callback_t) callback) 946 { 947 try { 948 auto ptr = callback.passable(); 949 browser.runOnWebView((wv) { 950 getOpenFileName(wv.parentWindow, (string name) { 951 auto callback = RC!cef_file_dialog_callback_t(ptr); 952 auto list = libcef.string_list_alloc(); 953 auto item = cef_string_t(name); 954 libcef.string_list_append(list, &item); 955 callback.cont(list); 956 }, null, null, () { 957 auto callback = RC!cef_file_dialog_callback_t(ptr); 958 callback.cancel(); 959 }, "/home/me/"); 960 }); 961 } catch(Exception e) {} 962 963 return 1; 964 } 965 } 966 967 class MiniguiDownloadHandler : CEF!cef_download_handler_t { 968 override int on_before_download( 969 RC!cef_browser_t browser, 970 RC!cef_download_item_t download_item, 971 const(cef_string_t)* suggested_name, 972 RC!cef_before_download_callback_t callback 973 ) nothrow 974 { 975 // FIXME: different filename and check if exists for overwrite etc 976 auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length])); 977 sdpyPrintDebugString(fn.toGC); 978 callback.cont(&fn, false); 979 980 return 1; 981 } 982 983 override void on_download_updated( 984 RC!cef_browser_t browser, 985 RC!cef_download_item_t download_item, 986 RC!cef_download_item_callback_t cancel 987 ) nothrow 988 { 989 sdpyPrintDebugString(download_item.get_percent_complete()); 990 // FIXME 991 } 992 993 override int can_download(RC!(cef_browser_t), const(cef_string_utf16_t)*, const(cef_string_utf16_t)*) { 994 return 1; 995 } 996 } 997 998 class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t { 999 override int on_pre_key_event( 1000 RC!(cef_browser_t) browser, 1001 const(cef_key_event_t)* event, 1002 XEvent* osEvent, 1003 int* isShortcut 1004 ) nothrow { 1005 /+ 1006 sdpyPrintDebugString("---pre---"); 1007 sdpyPrintDebugString(event.focus_on_editable_field); 1008 sdpyPrintDebugString(event.windows_key_code); 1009 sdpyPrintDebugString(event.modifiers); 1010 sdpyPrintDebugString(event.unmodified_character); 1011 +/ 1012 //*isShortcut = 1; 1013 return 0; // 1 if handled, which cancels sending it to browser 1014 } 1015 1016 override int on_key_event( 1017 RC!(cef_browser_t) browser, 1018 const(cef_key_event_t)* event, 1019 XEvent* osEvent 1020 ) nothrow { 1021 /+ 1022 sdpyPrintDebugString("---key---"); 1023 sdpyPrintDebugString(event.focus_on_editable_field); 1024 sdpyPrintDebugString(event.windows_key_code); 1025 sdpyPrintDebugString(event.modifiers); 1026 +/ 1027 return 0; // 1 if handled 1028 } 1029 } 1030 1031 class MiniguiDisplayHandler : CEF!cef_display_handler_t { 1032 override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) { 1033 auto url = address.toGC; 1034 browser.runOnWebView((wv) { 1035 wv.url = url; 1036 }); 1037 } 1038 override void on_title_change(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* title) { 1039 auto t = title.toGC; 1040 browser.runOnWebView((wv) { 1041 wv.title = t; 1042 }); 1043 } 1044 override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) { 1045 string url; 1046 auto size = libcef.string_list_size(urls); 1047 if(size > 0) { 1048 cef_string_t str; 1049 libcef.string_list_value(urls, 0, &str); 1050 url = str.toGC; 1051 1052 static class Thing : CEF!cef_download_image_callback_t { 1053 RC!cef_browser_t browserHandle; 1054 this(RC!cef_browser_t browser) nothrow { 1055 this.browserHandle = browser; 1056 } 1057 override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow { 1058 int width; 1059 int height; 1060 if(image.getRawPointer is null) { 1061 browserHandle.runOnWebView((wv) { 1062 wv.favicon = null; 1063 }); 1064 return; 1065 } 1066 1067 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); 1068 1069 if(data.getRawPointer is null || width == 0 || height == 0) { 1070 browserHandle.runOnWebView((wv) { 1071 wv.favicon = null; 1072 }); 1073 } else { 1074 auto s = data.get_size(); 1075 auto buffer = new ubyte[](s); 1076 auto got = data.get_data(buffer.ptr, buffer.length, 0); 1077 auto slice = buffer[0 .. got]; 1078 1079 auto img = new TrueColorImage (width, height, slice); 1080 1081 browserHandle.runOnWebView((wv) { 1082 wv.favicon = img; 1083 }); 1084 } 1085 } 1086 } 1087 1088 if(url.length) { 1089 auto callback = new Thing(browser); 1090 1091 browser.get_host().download_image(&str, true, 16, 0, callback.passable); 1092 } else { 1093 browser.runOnWebView((wv) { 1094 wv.favicon = null; 1095 }); 1096 } 1097 } 1098 1099 browser.runOnWebView((wv) { 1100 wv.favicon_url = url; 1101 }); 1102 } 1103 override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) { 1104 } 1105 override int on_tooltip(RC!(cef_browser_t) browser, cef_string_utf16_t*) { 1106 return 0; 1107 } 1108 override void on_status_message(RC!(cef_browser_t) browser, const(cef_string_utf16_t)* msg) { 1109 auto status = msg.toGC; 1110 browser.runOnWebView((wv) { 1111 wv.status = status; 1112 }); 1113 } 1114 override void on_loading_progress_change(RC!(cef_browser_t) browser, double progress) { 1115 // progress is from 0.0 to 1.0 1116 browser.runOnWebView((wv) { 1117 wv.loadingProgress = cast(int) (progress * 100); 1118 }); 1119 } 1120 override int on_console_message(RC!(cef_browser_t), cef_log_severity_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, int) { 1121 return 1; // 1 means to suppress it being automatically output 1122 } 1123 override int on_auto_resize(RC!(cef_browser_t), const(cef_size_t)*) { 1124 return 0; 1125 } 1126 override int on_cursor_change(RC!(cef_browser_t), cef_cursor_handle_t, cef_cursor_type_t, const(cef_cursor_info_t)*) { 1127 return 0; 1128 } 1129 override void on_media_access_change(RC!(cef_browser_t), int, int) { 1130 1131 } 1132 } 1133 1134 class MiniguiRequestHandler : CEF!cef_request_handler_t { 1135 1136 override int on_render_process_unresponsive(RC!(cef_browser_t), RC!(cef_unresponsive_process_callback_t)) nothrow { 1137 return 0; 1138 } 1139 override void on_render_process_responsive(RC!(cef_browser_t) p) nothrow { 1140 1141 } 1142 1143 override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow { 1144 return 0; 1145 } 1146 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 { 1147 return 0; 1148 } 1149 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 { 1150 return null; 1151 } 1152 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 { 1153 // this is for http basic auth popup..... 1154 return 0; 1155 } 1156 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 { 1157 return 0; 1158 } 1159 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 { 1160 return 0; 1161 } 1162 override void on_render_view_ready(RC!(cef_browser_t) p) nothrow { 1163 1164 } 1165 override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t, int error_code, const(cef_string_utf16_t)*) nothrow { 1166 1167 } 1168 override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow { 1169 browser.runOnWebView(delegate(wv) { 1170 wv.executeJavascript("console.log('here');"); 1171 }); 1172 1173 } 1174 } 1175 1176 class MiniguiContextMenuHandler : CEF!cef_context_menu_handler_t { 1177 private MiniguiCefClient client; 1178 this(MiniguiCefClient client) { 1179 this.client = client; 1180 } 1181 1182 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 { 1183 // FIXME: should i customize these? it is kinda specific to my browser 1184 int itemNo; 1185 1186 void addItem(string label, int commandNo) { 1187 auto lbl = cef_string_t(label); 1188 model.insert_item_at(/* index */ itemNo, /* command id */ cef_menu_id_t.MENU_ID_USER_FIRST + commandNo, &lbl); 1189 itemNo++; 1190 } 1191 1192 void addSeparator() { 1193 model.insert_separator_at(itemNo); 1194 itemNo++; 1195 } 1196 1197 auto flags = params.get_type_flags(); 1198 1199 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_LINK) { 1200 // cef_string_userfree_t linkUrl = params.get_unfiltered_link_url(); 1201 // toGCAndFree 1202 addItem("Open link in new window", 1); 1203 addItem("Copy link URL", 2); 1204 1205 // FIXME: open in other browsers 1206 // FIXME: open in ytv 1207 addSeparator(); 1208 } 1209 1210 if(flags & cef_context_menu_type_flags_t.CM_TYPEFLAG_MEDIA) { 1211 // cef_string_userfree_t linkUrl = params.get_source_url(); 1212 // toGCAndFree 1213 addItem("Open media in new window", 3); 1214 addItem("Copy source URL", 4); 1215 addItem("Download media", 5); 1216 addSeparator(); 1217 } 1218 1219 1220 // get_page_url 1221 // get_title_text 1222 // has_image_contents ??? 1223 // get_source_url 1224 // get_xcoord and get_ycoord 1225 // get_selection_text 1226 1227 } 1228 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 { 1229 // could do a custom display here if i want but i think it is good enough as it is 1230 return 0; 1231 } 1232 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 { 1233 switch(commandId) { 1234 case cef_menu_id_t.MENU_ID_USER_FIRST + 1: // open link in new window 1235 auto what = params.get_unfiltered_link_url().toGCAndFree(); 1236 1237 browser.runOnWebView((widget) { 1238 auto event = new NewWindowRequestedEvent(what, widget); 1239 event.dispatch(); 1240 }); 1241 return 1; 1242 case cef_menu_id_t.MENU_ID_USER_FIRST + 2: // copy link url 1243 auto what = params.get_link_url().toGCAndFree(); 1244 1245 browser.runOnWebView((widget) { 1246 auto event = new CopyRequestedEvent(what, widget); 1247 event.dispatch(); 1248 }); 1249 return 1; 1250 case cef_menu_id_t.MENU_ID_USER_FIRST + 3: // open media in new window 1251 auto what = params.get_source_url().toGCAndFree(); 1252 1253 browser.runOnWebView((widget) { 1254 auto event = new NewWindowRequestedEvent(what, widget); 1255 event.dispatch(); 1256 }); 1257 return 1; 1258 case cef_menu_id_t.MENU_ID_USER_FIRST + 4: // copy source url 1259 auto what = params.get_source_url().toGCAndFree(); 1260 1261 browser.runOnWebView((widget) { 1262 auto event = new CopyRequestedEvent(what, widget); 1263 event.dispatch(); 1264 }); 1265 return 1; 1266 case cef_menu_id_t.MENU_ID_USER_FIRST + 5: // download media 1267 auto str = cef_string_t(params.get_source_url().toGCAndFree()); 1268 browser.get_host().start_download(&str); 1269 return 1; 1270 default: 1271 return 0; 1272 } 1273 } 1274 override void on_context_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1275 // to close the custom display 1276 } 1277 1278 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 { 1279 return 0; 1280 } 1281 override int on_quick_menu_command(RC!(cef_browser_t), RC!(cef_frame_t), int, cef_event_flags_t) nothrow { 1282 return 0; 1283 } 1284 override void on_quick_menu_dismissed(RC!(cef_browser_t), RC!(cef_frame_t)) nothrow { 1285 1286 } 1287 } 1288 1289 class MiniguiFocusHandler : CEF!cef_focus_handler_t { 1290 override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow { 1291 // sdpyPrintDebugString("taking"); 1292 browser.runOnWebView(delegate(wv) { 1293 Widget f; 1294 if(next) { 1295 f = Window.getFirstFocusable(wv.parentWindow); 1296 } else { 1297 foreach(w; &wv.parentWindow.focusableWidgets) { 1298 if(w is wv) 1299 break; 1300 f = w; 1301 } 1302 } 1303 if(f) 1304 f.focus(); 1305 }); 1306 } 1307 override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { 1308 /+ 1309 browser.runOnWebView((ev) { 1310 ev.focus(); // even this can steal focus from other parts of my application! 1311 }); 1312 +/ 1313 // sdpyPrintDebugString("setting"); 1314 1315 // if either the parent window or the ozone window has the focus, we 1316 // can redirect it to the input focus. CEF calls this method sometimes 1317 // before setting the focus (where return 1 can override) and sometimes 1318 // after... which is totally inappropriate for it to do but it does anyway 1319 // and we want to undo the damage of this. 1320 browser.runOnWebView((ev) { 1321 arsd.simpledisplay.Window focus_window; 1322 int revert_to_return; 1323 XGetInputFocus(XDisplayConnection.get, &focus_window, &revert_to_return); 1324 if(focus_window is ev.parentWindow.win.impl.window || focus_window is ev.ozone) { 1325 // refocus our correct input focus 1326 ev.parentWindow.win.focus(); 1327 XSync(XDisplayConnection.get, 0); 1328 1329 // and then tell the chromium thing it still has it 1330 // so it will think it got it, lost it, then got it again 1331 // and hopefully not try to get it again 1332 XFocusChangeEvent eve; 1333 eve.type = arsd.simpledisplay.EventType.FocusIn; 1334 eve.display = XDisplayConnection.get; 1335 eve.window = ev.ozone; 1336 eve.mode = NotifyModes.NotifyNormal; 1337 eve.detail = NotifyDetail.NotifyVirtual; 1338 1339 // sdpyPrintDebugString("Sending FocusIn hack here"); 1340 1341 trapXErrors( { 1342 XSendEvent(XDisplayConnection.get, ev.ozone, false, 0, cast(XEvent*) &eve); 1343 }); 1344 1345 } 1346 }); 1347 1348 return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. 1349 // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent 1350 // even though things work fine if i always cancel except 1351 // it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh 1352 // it also breaks its own pop up menus and drop down boxes to allow this! wtf 1353 } 1354 override void on_got_focus(RC!(cef_browser_t) browser) nothrow { 1355 // sdpyPrintDebugString("got"); 1356 browser.runOnWebView((ev) { 1357 // this sometimes steals from the app too but it is relatively acceptable 1358 // steals when i mouse in from the side of the window quickly, but still 1359 // i want the minigui state to match so i'll allow it 1360 1361 //if(ev.parentWindow) ev.parentWindow.focus(); 1362 ev.focus(); 1363 }); 1364 } 1365 } 1366 1367 class MiniguiCefClient : CEF!cef_client_t { 1368 1369 void delegate(scope OpenNewWindowParams) openNewWindow; 1370 1371 MiniguiCefLifeSpanHandler lsh; 1372 MiniguiLoadHandler loadHandler; 1373 MiniguiDialogHandler dialogHandler; 1374 MiniguiDisplayHandler displayHandler; 1375 MiniguiDownloadHandler downloadHandler; 1376 MiniguiKeyboardHandler keyboardHandler; 1377 MiniguiFocusHandler focusHandler; 1378 MiniguiRequestHandler requestHandler; 1379 MiniguiContextMenuHandler contextMenuHandler; 1380 this(void delegate(scope OpenNewWindowParams) openNewWindow) { 1381 this.openNewWindow = openNewWindow; 1382 lsh = new MiniguiCefLifeSpanHandler(this); 1383 loadHandler = new MiniguiLoadHandler(); 1384 dialogHandler = new MiniguiDialogHandler(); 1385 displayHandler = new MiniguiDisplayHandler(); 1386 downloadHandler = new MiniguiDownloadHandler(); 1387 keyboardHandler = new MiniguiKeyboardHandler(); 1388 focusHandler = new MiniguiFocusHandler(); 1389 requestHandler = new MiniguiRequestHandler(); 1390 contextMenuHandler = new MiniguiContextMenuHandler(this); 1391 } 1392 1393 override cef_audio_handler_t* get_audio_handler() { 1394 return null; 1395 } 1396 override cef_context_menu_handler_t* get_context_menu_handler() { 1397 return contextMenuHandler.returnable; 1398 } 1399 override cef_dialog_handler_t* get_dialog_handler() { 1400 return dialogHandler.returnable; 1401 } 1402 override cef_display_handler_t* get_display_handler() { 1403 return displayHandler.returnable; 1404 } 1405 override cef_download_handler_t* get_download_handler() { 1406 return downloadHandler.returnable; 1407 } 1408 override cef_drag_handler_t* get_drag_handler() { 1409 return null; 1410 } 1411 override cef_find_handler_t* get_find_handler() { 1412 return null; 1413 } 1414 override cef_focus_handler_t* get_focus_handler() { 1415 return focusHandler.returnable; 1416 } 1417 override cef_jsdialog_handler_t* get_jsdialog_handler() { 1418 // needed for alert etc. 1419 return null; 1420 } 1421 override cef_keyboard_handler_t* get_keyboard_handler() { 1422 // this can handle keyboard shortcuts etc 1423 return keyboardHandler.returnable; 1424 } 1425 override cef_life_span_handler_t* get_life_span_handler() { 1426 return lsh.returnable; 1427 } 1428 override cef_load_handler_t* get_load_handler() { 1429 return loadHandler.returnable; 1430 } 1431 override cef_render_handler_t* get_render_handler() { 1432 // this thing might work for an off-screen thing 1433 // like to an image or to a video stream maybe 1434 // 1435 // might be useful to have it render here then send it over too for remote X sharing a process 1436 return null; 1437 } 1438 override cef_request_handler_t* get_request_handler() { 1439 return requestHandler.returnable; 1440 } 1441 override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) { 1442 return 0; // return 1 if you can actually handle the message 1443 } 1444 override cef_frame_handler_t* get_frame_handler() nothrow { 1445 return null; 1446 } 1447 override cef_print_handler_t* get_print_handler() nothrow { 1448 return null; 1449 } 1450 1451 override cef_command_handler_t* get_command_handler() { 1452 return null; 1453 } 1454 1455 override cef_permission_handler_t* get_permission_handler() { 1456 return null; 1457 } 1458 1459 } 1460 } 1461 1462 class BrowserClosedEvent : Event { 1463 enum EventString = "browserclosed"; 1464 1465 this(Widget target) { super(EventString, target); } 1466 override bool cancelable() const { return false; } 1467 } 1468 1469 class CopyRequestedEvent : Event { 1470 enum EventString = "browsercopyrequested"; 1471 1472 string what; 1473 1474 this(string what, Widget target) { this.what = what; super(EventString, target); } 1475 override bool cancelable() const { return false; } 1476 } 1477 1478 class NewWindowRequestedEvent : Event { 1479 enum EventString = "browserwindowrequested"; 1480 1481 string url; 1482 1483 this(string url, Widget target) { this.url = url; super(EventString, target); } 1484 override bool cancelable() const { return false; } 1485 } 1486 1487 1488 1489 /+ 1490 pragma(mangle, "_ZN12CefWindowX115FocusEv") 1491 //pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE") 1492 extern(C++) 1493 export void _ZN12CefWindowX115FocusEv() { 1494 sdpyPrintDebugString("OVERRIDDEN"); 1495 } 1496 +/