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