The OpenD Programming Language

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 +/