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