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