From 7f31dfbaceaeb4fc5c153630194721e29249212b Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sun, 3 Apr 2022 14:54:00 -0400 Subject: [PATCH 01/11] more webview support and lld-link compat hacks --- minigui.d | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/minigui.d b/minigui.d index e193b2e..17fdee9 100644 --- a/minigui.d +++ b/minigui.d @@ -327,6 +327,9 @@ version(Windows) { } version(Windows) { + version(minigui_manifest) {} else version=minigui_no_manifest; + + version(minigui_no_manifest) {} else static if(__VERSION__ >= 2_083) version(CRuntime_Microsoft) { // FIXME: mingw? // assume we want commctrl6 whenever possible since there's really no reason not to @@ -4204,6 +4207,19 @@ template classStaticallyEmits(This, EventType) { class NestedChildWindowWidget : Widget { SimpleWindow win; + /++ + Used on X to send focus to the appropriate child window when requested by the window manager. + + Normally returns its own nested window. Can also return another child or null to revert to the parent + if you override it in a child class. + + History: + Added April 2, 2022 (dub v10.8) + +/ + SimpleWindow focusableWindow() { + return win; + } + /// // win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent)); this(SimpleWindow win, Widget parent) { @@ -4236,6 +4252,18 @@ class NestedChildWindowWidget : Widget { return pwin; } + /++ + Called upon the nested window being destroyed. + Remember the window has already been destroyed at + this point, so don't use the native handle for anything. + + History: + Added April 3, 2022 (dub v10.8) + +/ + protected void dispose() { + + } + protected void windowsetup(SimpleWindow w) { /* win.onFocusChange = (bool getting) { @@ -4244,6 +4272,23 @@ class NestedChildWindowWidget : Widget { }; */ + /+ + win.onFocusChange = (bool getting) { + if(getting) { + this.parentWindow.focusedWidget = this; + this.emit!FocusEvent(); + this.emit!FocusInEvent(); + } else { + this.emit!BlurEvent(); + this.emit!FocusOutEvent(); + } + }; + +/ + + win.onDestroyed = () { + this.dispose(); + }; + version(win32_widgets) { Widget.nativeMapping[win.hwnd] = this; this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc); @@ -4259,8 +4304,7 @@ class NestedChildWindowWidget : Widget { parentWindow.dispatchMouseEvent(e); }, (KeyEvent e) { - //import std.stdio; - //writefln("%x %s", cast(uint) e.key, e.key); + //import std.stdio; writefln("%s %x %s", cast(void*) win, cast(uint) e.key, e.key); parentWindow.dispatchKeyEvent(e); }, (dchar e) { @@ -7926,11 +7970,21 @@ class Window : Widget { if(Runtime.args.length) title = Runtime.args[0]; } - win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow); + win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus); + + win.setRequestedInputFocus = &this.setRequestedInputFocus; this(win); } + private SimpleWindow setRequestedInputFocus() { + if(auto fw = cast(NestedChildWindowWidget) focusedWidget) { + // sdpyPrintDebugString("heaven"); + return fw.focusableWindow; + } + return win; + } + /// ditto this(string title, int width = 500, int height = 500) { this(width, height, title); @@ -9715,7 +9769,7 @@ class MenuBar : Widget { Status bars appear at the bottom of a MainWindow. They are made out of Parts, with a width and content. - They can have multiple parts or be in simple mode. FIXME: implement + They can have multiple parts or be in simple mode. FIXME: implement simple mode. sb.parts[0].content = "Status bar text!"; From a2d1e5df2d313ae621269255e47d58763d4a5ebd Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 16:27:18 -0400 Subject: [PATCH 02/11] peer verification overhaul --- http2.d | 194 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 172 insertions(+), 22 deletions(-) diff --git a/http2.d b/http2.d index 05e5f15..9d2cc42 100644 --- a/http2.d +++ b/http2.d @@ -102,7 +102,11 @@ unittest { // FIXME: multipart encoded file uploads needs implementation // future: do web client api stuff -__gshared bool defaultVerifyPeer = true; // public but intentionally undocumented +private __gshared bool defaultVerifyPeer_ = true; + +void defaultVerifyPeer(bool v) { + defaultVerifyPeer_ = v; +} debug import std.stdio; @@ -953,13 +957,30 @@ class HttpRequest { /++ Proxy to use for this request. It should be a URL or `null`. - This must be sent before you call `send`. + This must be sent before you call [send]. History: Added April 12, 2021 (dub v9.5) +/ string proxy; + /++ + For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be + verified. Setting this to `false` will skip this check and allow the connection to continue anyway. + + When the [HttpRequest] is constructed from a [HttpClient], it will inherit the value from the client + instead of using the `= true` here. You can change this value any time before you call [send] (which + is done implicitly if you call [waitForCompletion]). + + History: + Added April 5, 2022 (dub v10.8) + + Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes + even if it was true, it would skip the verification. Now, it always respects this local setting. + +/ + bool verifyPeer = true; + + /// Final url after any redirections string finalUrl; @@ -1215,13 +1236,13 @@ class HttpRequest { } } - Socket getOpenSocketOnHost(string proxy, string host, ushort port, bool ssl, string unixSocketPath) { + Socket getOpenSocketOnHost(string proxy, string host, ushort port, bool ssl, string unixSocketPath, bool verifyPeer) { Socket openNewConnection() { Socket socket; if(ssl) { version(with_openssl) { loadOpenSsl(); - socket = new SslClientSocket(family(unixSocketPath), SocketType.STREAM, host, defaultVerifyPeer); + socket = new SslClientSocket(family(unixSocketPath), SocketType.STREAM, host, verifyPeer); } else throw new Exception("SSL not compiled in"); } else @@ -1449,7 +1470,7 @@ class HttpRequest { Socket socket; try { - socket = getOpenSocketOnHost(pc.proxy, pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl, pc.requestParameters.unixSocketPath); + socket = getOpenSocketOnHost(pc.proxy, pc.requestParameters.host, pc.requestParameters.port, pc.requestParameters.ssl, pc.requestParameters.unixSocketPath, pc.verifyPeer); } catch(ProxyException e) { // connection refused or timed out (I should disambiguate somehow)... pc.state = HttpRequest.State.aborted; @@ -1474,7 +1495,7 @@ class HttpRequest { removeFromPending[removeFromPendingCount++] = pc; continue; } catch(Exception e) { - // connection failed due to other user error + // connection failed due to other user error or SSL (i should disambiguate somehow)... pc.state = HttpRequest.State.aborted; pc.responseData.code = 2; @@ -2226,7 +2247,7 @@ struct HttpRequestsAsTheyComplete { } } -/// +// struct HttpRequestParameters { // FIXME: implement these //Duration timeoutTotal; // the whole request must finish in this time or else it fails,even if data is still trickling in @@ -2255,7 +2276,7 @@ struct HttpRequestParameters { string contentType; /// ubyte[] bodyData; /// - string unixSocketPath; + string unixSocketPath; /// } interface IHttpClient { @@ -2333,6 +2354,15 @@ class HttpClient { this.certFormat = certFormat; } + /++ + Sets whether [HttpRequest]s created through this object (with [navigateTo], [request], etc.), will have the + value of [HttpRequest.verifyPeer] of true or false upon construction. + + History: + Added April 5, 2022 (dub v10.8). Previously, there was an undocumented global value used. + +/ + bool defaultVerifyPeer = true; + // FIXME: try to not make these static private static string certFilename; private static string keyFilename; @@ -2387,6 +2417,8 @@ class HttpClient { auto request = new HttpRequest(this, uri, method, cache, defaultTimeout, proxyToUse); + request.verifyPeer = this.defaultVerifyPeer; + request.requestParameters.userAgent = userAgent; request.requestParameters.authorization = authorization; @@ -2418,7 +2450,11 @@ class HttpClient { private string currentDomain; private ICache cache; + /++ + + +/ this(ICache cache = null) { + this.defaultVerifyPeer = .defaultVerifyPeer_; this.cache = cache; loadDefaultProxy(); } @@ -2748,6 +2784,7 @@ version(use_openssl) { struct SSL_CTX {} struct SSL_METHOD {} enum SSL_VERIFY_NONE = 0; + enum SSL_VERIFY_PEER = 1; struct ossllib { __gshared static extern(C) { @@ -2780,6 +2817,16 @@ version(use_openssl) { void function(SSL_CTX*, void function(SSL*, char* line)) SSL_CTX_set_keylog_callback; + int function(SSL_CTX*) SSL_CTX_set_default_verify_paths; + + X509_STORE* function(SSL_CTX*) SSL_CTX_get_cert_store; + c_long function(const SSL* ssl) SSL_get_verify_result; + + /+ + SSL_CTX_load_verify_locations + SSL_CTX_set_client_CA_list + +/ + // client cert things void function (SSL_CTX *ctx, int function(SSL *ssl, X509 **x509, EVP_PKEY **pkey)) SSL_CTX_set_client_cert_cb; @@ -2791,6 +2838,7 @@ version(use_openssl) { alias pem_password_cb = int function(char* buffer, int bufferSize, int rwflag, void* userPointer); struct X509; + struct X509_STORE; struct EVP_PKEY; import core.stdc.config; @@ -2804,7 +2852,10 @@ version(use_openssl) { void function(ulong, void*) OPENSSL_init_crypto; - void function(FILE*) ERR_print_errors_fp; + void function(print_errors_cb, void*) ERR_print_errors_cb; + + void function(X509*) X509_free; + int function(X509_STORE*, X509*) X509_STORE_add_cert; X509* function(FILE *fp, X509 **x, pem_password_cb *cb, void *u) PEM_read_X509; @@ -2812,9 +2863,31 @@ version(use_openssl) { EVP_PKEY* function(FILE *fp, EVP_PKEY **a) d2i_PrivateKey_fp; X509* function(FILE *fp, X509 **x) d2i_X509_fp; + + X509* function(X509** a, const(ubyte*)* pp, c_long length) d2i_X509; } } + extern(C) + alias print_errors_cb = int function(const char*, size_t, void*); + + int SSL_CTX_set_default_verify_paths(SSL_CTX* a) { + if(ossllib.SSL_CTX_set_default_verify_paths) + return ossllib.SSL_CTX_set_default_verify_paths(a); + else throw new Exception("SSL_CTX_set_default_verify_paths not loaded"); + } + + c_long SSL_get_verify_result(const SSL* ssl) { + if(ossllib.SSL_get_verify_result) + return ossllib.SSL_get_verify_result(ssl); + else throw new Exception("SSL_get_verify_result not loaded"); + } + + X509_STORE* SSL_CTX_get_cert_store(SSL_CTX* a) { + if(ossllib.SSL_CTX_get_cert_store) + return ossllib.SSL_CTX_get_cert_store(a); + else throw new Exception("SSL_CTX_get_cert_store not loaded"); + } SSL_CTX* SSL_CTX_new(const SSL_METHOD* a) { if(ossllib.SSL_CTX_new) @@ -2841,6 +2914,12 @@ version(use_openssl) { else throw new Exception("SSL_CTX_set_client_cert_cb not loaded"); } + X509* d2i_X509(X509** a, const(ubyte*)* pp, c_long length) { + if(eallib.d2i_X509) + return eallib.d2i_X509(a, pp, length); + else throw new Exception("d2i_X509 not loaded"); + } + X509* PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u) { if(eallib.PEM_read_X509) return eallib.PEM_read_X509(fp, x, cb, u); @@ -2920,10 +2999,29 @@ version(use_openssl) { return ossllib.TLS_client_method(); else throw new Exception("TLS_client_method not loaded"); } - void ERR_print_errors_fp(FILE* a) { - if(eallib.ERR_print_errors_fp) - return eallib.ERR_print_errors_fp(a); - else throw new Exception("ERR_print_errors_fp not loaded"); + void ERR_print_errors_cb(print_errors_cb cb, void* u) { + if(eallib.ERR_print_errors_cb) + return eallib.ERR_print_errors_cb(cb, u); + else throw new Exception("ERR_print_errors_cb not loaded"); + } + void X509_free(X509* x) { + if(eallib.X509_free) + return eallib.X509_free(x); + else throw new Exception("X509_free not loaded"); + } + int X509_STORE_add_cert(X509_STORE* s, X509* x) { + if(eallib.X509_STORE_add_cert) + return eallib.X509_STORE_add_cert(s, x); + else throw new Exception("X509_STORE_add_cert not loaded"); + } + + extern(C) + int collectSslErrors(const char* ptr, size_t len, void* user) @trusted { + string* s = cast(string*) user; + + (*s) ~= ptr[0 .. len]; + + return 0; } extern(C) @@ -3068,14 +3166,22 @@ version(use_openssl) { private void initSsl(bool verifyPeer, string hostname) { ctx = SSL_CTX_new(SSLv23_client_method()); assert(ctx !is null); + + SSL_CTX_set_default_verify_paths(ctx); + version(Windows) + loadCertificatesFromRegistry(ctx); + debug SSL_CTX_keylog_cb_func(ctx, &write_to_file); ssl = SSL_new(ctx); if(hostname.length) - SSL_set_tlsext_host_name(ssl, toStringz(hostname)); + SSL_set_tlsext_host_name(ssl, toStringz(hostname)); - if(!verifyPeer) + if(verifyPeer) + SSL_set_verify(ssl, SSL_VERIFY_PEER, null); + else SSL_set_verify(ssl, SSL_VERIFY_NONE, null); + SSL_set_fd(ssl, cast(int) this.handle); // on win64 it is necessary to truncate, but the value is never large anyway see http://openssl.6102.n7.nabble.com/Sockets-windows-64-bit-td36169.html @@ -3132,11 +3238,13 @@ version(use_openssl) { @trusted void do_ssl_connect() { if(SSL_connect(ssl) == -1) { - ERR_print_errors_fp(core.stdc.stdio.stderr); + string str; + ERR_print_errors_cb(&collectSslErrors, &str); int i; + auto err = SSL_get_verify_result(ssl); //printf("wtf\n"); //scanf("%d\n", i); - throw new Exception("ssl connect"); + throw new Exception("ssl connect failed " ~ str ~ " // " ~ to!string(err)); } } @@ -3146,11 +3254,12 @@ version(use_openssl) { debug(arsd_http2_verbose) writeln("ssl writing ", buf.length); auto retval = SSL_write(ssl, buf.ptr, cast(uint) buf.length); if(retval == -1) { - ERR_print_errors_fp(core.stdc.stdio.stderr); + string str; + ERR_print_errors_cb(&collectSslErrors, &str); int i; //printf("wtf\n"); //scanf("%d\n", i); - throw new Exception("ssl send"); + throw new Exception("ssl send failed " ~ str); } return retval; @@ -3165,11 +3274,12 @@ version(use_openssl) { auto retval = SSL_read(ssl, buf.ptr, cast(int)buf.length); debug(arsd_http2_verbose) writeln("ssl_read after"); if(retval == -1) { - ERR_print_errors_fp(core.stdc.stdio.stderr); + string str; + ERR_print_errors_cb(&collectSslErrors, &str); int i; //printf("wtf\n"); //scanf("%d\n", i); - throw new Exception("ssl receive"); + throw new Exception("ssl receive failed " ~ str); } return retval; } @@ -3664,7 +3774,7 @@ class WebSocket { if(ssl) { version(with_openssl) { loadOpenSsl(); - socket = new SslClientSocket(family(uri.unixSocketPath), SocketType.STREAM, host, defaultVerifyPeer); + socket = new SslClientSocket(family(uri.unixSocketPath), SocketType.STREAM, host, config.verifyPeer); } else throw new Exception("SSL not compiled in"); } else @@ -4018,6 +4128,18 @@ class WebSocket { Added March 31, 2021 (included in dub version 9.4) +/ Duration timeoutFromInactivity = 1.minutes; + + /++ + For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be + verified. Setting this to `false` will skip this check and allow the connection to continue anyway. + + History: + Added April 5, 2022 (dub v10.8) + + Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes + even if it was true, it would skip the verification. Now, it always respects this local setting. + +/ + bool verifyPeer = true; } /++ @@ -4676,6 +4798,34 @@ public { } version(Windows) { + pragma(lib, "crypt32"); + import core.sys.windows.wincrypt; + extern(Windows) + PCCERT_CONTEXT CertEnumCertificatesInStore(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); + + void loadCertificatesFromRegistry(SSL_CTX* ctx) { + + auto store = CertOpenSystemStore(0, "ROOT"); + if(store is null) + return; + scope(exit) + CertCloseStore(store, 0); + + X509_STORE* ssl_store = SSL_CTX_get_cert_store(ctx); + PCCERT_CONTEXT c; + while((c = CertEnumCertificatesInStore(store, c)) !is null) { + const(ubyte)* thing = c.pbCertEncoded; + auto x509 = d2i_X509(null, &thing, c.cbCertEncoded); + if (x509) { + auto success = X509_STORE_add_cert(ssl_store, x509); + X509_free(x509); + } + } + + CertFreeCertificateContext(c); + } + + // because i use the FILE* in PEM_read_X509 and friends // gotta use this to bridge the MS C runtime functions // might be able to just change those to only use the BIO versions From d4ac2707c6ea86537ddadea852b9046d87823772 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 16:32:54 -0400 Subject: [PATCH 03/11] minor tweaks to old code --- audio.d | 6 ++---- engine.d | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/audio.d b/audio.d index 41f5436..b053100 100644 --- a/audio.d +++ b/audio.d @@ -4,8 +4,6 @@ module arsd.audio; import sdl.SDL; import sdl.SDL_mixer; -import std.string; - import arsd.engine; bool audioIsLoaded; // potential hack material @@ -15,7 +13,7 @@ class Sound { this(char[] filename){ if(!audioIsLoaded) return; - sfx = Mix_LoadWAV(std.string.toStringz(filename)); + sfx = Mix_LoadWAV((filename ~ "\0").ptr); if(sfx is null) throw new Exception(immutableString("Sound load " ~ filename)); } @@ -45,7 +43,7 @@ class Music { this(char[] filename){ if(!audioIsLoaded) return; - mus = Mix_LoadMUS(std.string.toStringz(filename)); + mus = Mix_LoadMUS((filename~"\0").ptr); if(mus is null) throw new Exception(immutableString("Music load " ~ filename)); } diff --git a/engine.d b/engine.d index 0da83ad..d9297d0 100644 --- a/engine.d +++ b/engine.d @@ -21,13 +21,13 @@ pragma(lib, "GL"); import sdl.SDL; import sdl.SDL_net; -import std.string; version(D_Version2) { import random = core.stdc.stdlib; alias random.srand srand; - import std.conv; - char[] convToString(T)(T t) { return to!(char[])(t); } + char[] convToString(int a) { + return null; + } string immutableString(in char[] a) { return a.idup; } } else { import random = std.random; @@ -49,7 +49,12 @@ version(D_Version2) else import std.stdarg; -import std.stdio; +version(D_Version2) { + import core.stdc.stdio; + void writefln(string s) { printf("%*s", s.length, s.ptr); } + void writefln(string s, int i) { printf(s.ptr, i); } +} else + import std.stdio; //version(linux) pragma(lib, "kbhit.o"); int randomNumber(int min, int max){ @@ -317,7 +322,7 @@ class Engine{ IPaddress ip; - if(SDLNet_ResolveHost(&ip, std.string.toStringz(whom), NET_PORT) == -1) + if(SDLNet_ResolveHost(&ip, (whom~"\0").ptr, NET_PORT) == -1) throw new Exception("Resolve host"); clientsock = SDLNet_TCP_Open(&ip); @@ -661,7 +666,7 @@ class Engine{ } void setTitle(in char[] title){ - SDL_WM_SetCaption(std.string.toStringz(title), null); + SDL_WM_SetCaption((title~"\0").ptr, null); } bool buttonWasPressed(Buttons button, int which = 0){ @@ -979,7 +984,7 @@ class Engine{ buttonLagQueueEnd[button][which] = 0; } else { if(when < globalTimer) - throw new Exception(immutableString("Impossible control timing " ~ convToString(when) ~ " @ " ~ convToString(globalTimer))); + throw new Exception(immutableString("Impossible control timing"));// " ~ convToString(when) ~ " @ " ~ convToString(globalTimer))); buttonsDown[button][which] = type; buttonsChecked[button][which] = false; } From 0a57b5dbe984a19a708ae598bf471d52bcd1d884 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 16:33:22 -0400 Subject: [PATCH 04/11] enable backspace from kernel line buffer --- terminalemulator.d | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terminalemulator.d b/terminalemulator.d index 7368337..dcf4abd 100644 --- a/terminalemulator.d +++ b/terminalemulator.d @@ -3628,6 +3628,12 @@ version(Posix) { argv[args.length] = null; + termios info; + ubyte[128] hack; // jic that druntime definition is still wrong + tcgetattr(master, &info); + info.c_cc[VERASE] = '\b'; + tcsetattr(master, TCSANOW, &info); + core.sys.posix.unistd.execv(argv[0], argv); } else { childrenAlive = 1; From 62a1544bda05da385b4c4718e3cb03e6c7ea7cd6 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 19:15:36 -0400 Subject: [PATCH 05/11] convenience method --- minigui.d | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/minigui.d b/minigui.d index 17fdee9..d13aea8 100644 --- a/minigui.d +++ b/minigui.d @@ -7619,6 +7619,17 @@ class Window : Widget { win.releaseInputGrab(); } + /++ + Sets the window icon which is often seen in title bars and taskbars. + + History: + Added April 5, 2022 (dub v10.8) + +/ + @property void icon(MemoryImage icon) { + if(win && icon) + win.icon = icon; + } + /// @scriptable @property bool focused() { From 6560878cd078329345320a06c092c9fc1537d66a Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 19:16:03 -0400 Subject: [PATCH 06/11] null check to avoid oops --- simpledisplay.d | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/simpledisplay.d b/simpledisplay.d index 5da96dd..c9eff53 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -2897,8 +2897,10 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { private Image secret_icon_inner; - /// Set the icon that is seen in the title bar or taskbar, etc., for the user. + /// Set the icon that is seen in the title bar or taskbar, etc., for the user. If passed `null`, does nothing. @property void icon(MemoryImage icon) { + if(icon is null) + return; auto tci = icon.getAsTrueColorImage(); version(Windows) { winIcon = new WindowsIcon(icon); From ce2f7d5f94e8f3d3edc46e0d394c098f6c04be25 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 19:16:15 -0400 Subject: [PATCH 07/11] more browser stuff, still not stable! --- minigui_addons/webview.d | 400 +++++++++++++++++++++++++++++++++++---- webview.d | 5 +- 2 files changed, 371 insertions(+), 34 deletions(-) diff --git a/minigui_addons/webview.d b/minigui_addons/webview.d index dddae37..e9a7741 100644 --- a/minigui_addons/webview.d +++ b/minigui_addons/webview.d @@ -34,7 +34,6 @@ version(linux) version(Windows) version=wv2; - /+ SPA mode: put favicon on top level window, no other user controls at top level, links to different domains always open in new window. +/ @@ -64,8 +63,14 @@ class WebViewWidgetBase : NestedChildWindowWidget { mixin Observable!(string, "title"); mixin Observable!(string, "url"); mixin Observable!(string, "status"); + + // not implemented on WV2 mixin Observable!(int, "loadingProgress"); + // not implemented on WV2 + mixin Observable!(string, "favicon_url"); + mixin Observable!(MemoryImage, "favicon"); // please note it can be changed to null! + abstract void refresh(); abstract void back(); abstract void forward(); @@ -74,6 +79,8 @@ class WebViewWidgetBase : NestedChildWindowWidget { abstract void navigate(string url); // the url and line are for error reporting purposes. They might be ignored. + // FIXME: add a callback with the reply. this can send a message from the js thread in cef and just ExecuteScript inWV2 + // FIXME: add AddScriptToExecuteOnDocumentCreated for cef.... abstract void executeJavascript(string code, string url = null, int line = 0); // for injecting stuff into the context // abstract void executeJavascriptBeforeEachLoad(string code); @@ -99,6 +106,8 @@ class WebViewWidgetBase : NestedChildWindowWidget { // AddScriptToExecuteOnDocumentCreated + + version(wv2) class WebViewWidget_WV2 : WebViewWidgetBase { private RC!ICoreWebView2 webview_window; @@ -107,7 +116,8 @@ class WebViewWidget_WV2 : WebViewWidgetBase { private bool initialized; - this(string url, Widget parent) { + this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, Widget parent) { + // FIXME: openNewWindow super(parent); // that ctor sets containerWindow @@ -207,17 +217,52 @@ class WebViewWidget_WV2 : WebViewWidgetBase { } } +/++ + The openInNewWindow delegate is given these params. + + To accept the new window, call + + params.accept(parent_widget); + + Please note, you can force it to replace the current tab + by just navigating your current thing to the given url instead + of accepting it. + + If you accept with a null widget, it will create a new window + but then return null, since the new window is managed by the + underlying webview instead of by minigui. + + If you do not call accept, the pop up will be blocked. + + accept returns an instance to the newly created widget, which will + be a parent to the widget you passed. + + accept will be called from the gui thread and it MUST not call into + any other webview methods. It should only create windows/widgets + and set event handlers etc. + + You MUST not escape references to anything in this structure. It + is entirely strictly temporary! ++/ +struct OpenNewWindowParams { + string url; + WebViewWidget delegate(Widget parent, bool enableJavascript = true) accept; +} + version(cef) class WebViewWidget_CEF : WebViewWidgetBase { + /++ + Create a webview that does not support opening links in new windows. + +/ this(string url, Widget parent) { + this(url, null, parent); + } + + this(string url, void delegate(scope OpenNewWindowParams) openNewWindow, Widget parent) { //semaphore = new Semaphore; assert(CefApp.active); - super(parent); - - flushGui(); - - mapping[containerWindow.nativeWindowHandle()] = this; + this(new MiniguiCefClient(openNewWindow), parent); cef_window_info_t window_info; window_info.parent_window = containerWindow.nativeWindowHandle; @@ -227,29 +272,77 @@ class WebViewWidget_CEF : WebViewWidgetBase { cef_browser_settings_t browser_settings; browser_settings.size = cef_browser_settings_t.sizeof; - client = new MiniguiCefClient(); + browser_settings.standard_font_family = cef_string_t("DejaVu Sans"); + browser_settings.fixed_font_family = cef_string_t("DejaVu Sans Mono"); + browser_settings.serif_font_family = cef_string_t("DejaVu Sans"); + browser_settings.sans_serif_font_family = cef_string_t("DejaVu Sans"); + browser_settings.cursive_font_family = cef_string_t("DejaVu Sans"); + browser_settings.fantasy_font_family = cef_string_t("DejaVu Sans"); + + browser_settings.remote_fonts = cef_state_t.STATE_DISABLED; auto got = libcef.browser_host_create_browser(&window_info, client.passable, &cef_url, &browser_settings, null, null); + } - /+ - containerWindow.closeQuery = delegate() { - browserHandle.get_host.close_browser(true); - //containerWindow.close(); - }; - +/ + /+ + ~this() { + import core.stdc.stdio; + import core.memory; + printf("CLEANUP %s\n", GC.inFinalizer ? "GC".ptr : "destroy".ptr); + } + +/ + override void dispose() { + // sdpyPrintDebugString("closed"); + // the window is already gone so too late to do this really.... + // if(browserHandle) browserHandle.get_host.close_browser(true); + + // sdpyPrintDebugString("DISPOSE"); + + if(win && win.nativeWindowHandle()) + mapping.remove(win.nativeWindowHandle()); + if(browserWindow) + browserMapping.remove(browserWindow); + + .destroy(this); // but this is ok to do some memory management cleanup + } + + private this(MiniguiCefClient client, Widget parent) { + super(parent); + + this.client = client; + + flushGui(); + + mapping[containerWindow.nativeWindowHandle()] = this; + + + this.parentWindow.addEventListener((FocusEvent fe) { + if(!browserHandle) return; + //browserHandle.get_host.set_focus(true); + + executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));"); + }); + this.parentWindow.addEventListener((BlurEvent be) { + if(!browserHandle) return; + + executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));"); + }); + + bool closeAttempted = false; + + this.parentWindow.addEventListener((scope ClosingEvent ce) { + if(!closeAttempted && browserHandle) { + browserHandle.get_host.close_browser(true); + ce.preventDefault(); + // sdpyPrintDebugString("closing"); + } + closeAttempted = true; + }); } private MiniguiCefClient client; - /+ - override void close() { - // FIXME: this should prolly be on the onclose event instead - mapping.remove[win.nativeWindowHandle()]; - super.close(); - } - +/ - override void registerMovementAdditionalWork() { if(browserWindow) { static if(UsingSimpledisplayX11) @@ -258,6 +351,12 @@ class WebViewWidget_CEF : WebViewWidgetBase { } } + SimpleWindow browserWindowWrapped; + override SimpleWindow focusableWindow() { + if(browserWindowWrapped is null && browserWindow) + browserWindowWrapped = new SimpleWindow(browserWindow); + return browserWindowWrapped; + } private NativeWindowHandle browserWindow; private RC!cef_browser_t browserHandle; @@ -311,6 +410,10 @@ version(cef) { +/ void runOnWebView(RC!cef_browser_t browser, void delegate(WebViewWidget) dg) nothrow { auto wh = cast(NativeWindowHandle) browser.get_host.get_window_handle; + + import core.thread; + try { thread_attachThis(); } catch(Exception e) {} + runInGuiThreadAsync({ if(auto wvp = wh in WebViewWidget.browserMapping) { dg(*wvp); @@ -321,13 +424,68 @@ version(cef) { } class MiniguiCefLifeSpanHandler : CEF!cef_life_span_handler_t { - override int on_before_popup(RC!cef_browser_t, RC!cef_frame_t, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, cef_window_open_disposition_t, int, const(cef_popup_features_t)*, cef_window_info_t*, cef_client_t**, cef_browser_settings_t*, cef_dictionary_value_t**, int*) { - return 0; + private MiniguiCefClient client; + this(MiniguiCefClient client) { + this.client = client; + } + + override int on_before_popup( + RC!cef_browser_t browser, + RC!cef_frame_t frame, + const(cef_string_t)* target_url, + const(cef_string_t)* target_frame_name, + cef_window_open_disposition_t target_disposition, + int user_gesture, + const(cef_popup_features_t)* popupFeatures, + cef_window_info_t* windowInfo, + cef_client_t** client, + cef_browser_settings_t* settings, + cef_dictionary_value_t** extra_info, + int* no_javascript_access + ) { + + if(this.client.openNewWindow is null) + return 1; // new windows disabled + + try { + int ret; + + import core.thread; + try { thread_attachThis(); } catch(Exception e) {} + + runInGuiThread({ + ret = 1; + scope WebViewWidget delegate(Widget, bool) o = (parent, enableJavascript) { + ret = 0; + if(parent !is null) { + auto widget = new WebViewWidget_CEF(this.client, parent); + (*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle; + + this.client.listOfWidgets ~= widget; + return widget; + } + return null; + }; + this.client.openNewWindow(OpenNewWindowParams("", o)); + return; + }); + + return ret; + } catch(Exception e) { + return 1; + } + /+ + if(!user_gesture) + return 1; // if not created by the user, cancel it; basic popup blocking + +/ } override void on_after_created(RC!cef_browser_t browser) { auto handle = cast(NativeWindowHandle) browser.get_host().get_window_handle(); auto ptr = browser.passable; // this adds to the refcount until it gets inside + import core.thread; + try { thread_attachThis(); } catch(Exception e) {} + // the only reliable key (at least as far as i can tell) is the window handle // so gonna look that up and do the sync mapping that way. runInGuiThreadAsync({ @@ -349,6 +507,16 @@ version(cef) { wv.browserWindow = handle; wv.browserHandle = RC!cef_browser_t(ptr); + wv.browserWindowWrapped = new SimpleWindow(wv.browserWindow); + /+ + XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask); + + wv.browserWindowWrapped.onFocusChange = (bool got) { + import std.format; + sdpyPrintDebugString(format("focus change %s %x", got, wv.browserWindowWrapped.impl.window)); + }; + +/ + wv.registerMovementAdditionalWork(); WebViewWidget.browserMapping[handle] = wv; @@ -356,7 +524,11 @@ version(cef) { }); } override int do_close(RC!cef_browser_t browser) { - return 0; + browser.runOnWebView((wv) { + auto bce = new BrowserClosedEvent(wv); + bce.dispatch(); + }); + return 1; } override void on_before_close(RC!cef_browser_t browser) { /+ @@ -389,7 +561,7 @@ version(cef) { 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, cef_string_list_t accept_filters, int selected_accept_filter, RC!(cef_file_dialog_callback_t) callback) { try { auto ptr = callback.passable(); - runInGuiThreadAsync({ + browser.runOnWebView((wv) { getOpenFileName((string name) { auto callback = RC!cef_file_dialog_callback_t(ptr); auto list = libcef.string_list_alloc(); @@ -407,6 +579,64 @@ version(cef) { } } + class MiniguiDownloadHandler : CEF!cef_download_handler_t { + override void on_before_download( + RC!cef_browser_t browser, + RC!cef_download_item_t download_item, + const(cef_string_t)* suggested_name, + RC!cef_before_download_callback_t callback + ) nothrow + { + // FIXME: different filename and check if exists for overwrite etc + auto fn = cef_string_t(cast(wstring)("/home/me/Downloads/"w ~ suggested_name.str[0..suggested_name.length])); + sdpyPrintDebugString(fn.toGC); + callback.cont(&fn, false); + } + + override void on_download_updated( + RC!cef_browser_t browser, + RC!cef_download_item_t download_item, + RC!cef_download_item_callback_t cancel + ) nothrow + { + sdpyPrintDebugString(download_item.get_percent_complete()); + // FIXME + } + } + + class MiniguiKeyboardHandler : CEF!cef_keyboard_handler_t { + override int on_pre_key_event( + RC!(cef_browser_t) browser, + const(cef_key_event_t)* event, + XEvent* osEvent, + int* isShortcut + ) nothrow { + /+ + sdpyPrintDebugString("---pre---"); + sdpyPrintDebugString(event.focus_on_editable_field); + sdpyPrintDebugString(event.windows_key_code); + sdpyPrintDebugString(event.modifiers); + sdpyPrintDebugString(event.unmodified_character); + +/ + //*isShortcut = 1; + return 0; // 1 if handled, which cancels sending it to browser + } + + override int on_key_event( + RC!(cef_browser_t) browser, + const(cef_key_event_t)* event, + XEvent* osEvent + ) nothrow { + /+ + sdpyPrintDebugString("---key---"); + sdpyPrintDebugString(event.focus_on_editable_field); + sdpyPrintDebugString(event.windows_key_code); + sdpyPrintDebugString(event.modifiers); + +/ + return 0; // 1 if handled + } + } + class MiniguiDisplayHandler : CEF!cef_display_handler_t { override void on_address_change(RC!(cef_browser_t) browser, RC!(cef_frame_t), const(cef_string_utf16_t)* address) { auto url = address.toGC; @@ -420,7 +650,64 @@ version(cef) { wv.title = t; }); } - override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t) { + override void on_favicon_urlchange(RC!(cef_browser_t) browser, cef_string_list_t urls) { + string url; + auto size = libcef.string_list_size(urls); + if(size > 0) { + cef_string_t str; + libcef.string_list_value(urls, 0, &str); + url = str.toGC; + + static class Thing : CEF!cef_download_image_callback_t { + RC!cef_browser_t browserHandle; + this(RC!cef_browser_t browser) nothrow { + this.browserHandle = browser; + } + override void on_download_image_finished(const(cef_string_t)* image_url, int http_status_code, RC!cef_image_t image) nothrow { + int width; + int height; + if(image.getRawPointer is null) { + browserHandle.runOnWebView((wv) { + wv.favicon = null; + }); + return; + } + + 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); + + if(data.getRawPointer is null || width == 0 || height == 0) { + browserHandle.runOnWebView((wv) { + wv.favicon = null; + }); + } else { + auto s = data.get_size(); + auto buffer = new ubyte[](s); + auto got = data.get_data(buffer.ptr, buffer.length, 0); + auto slice = buffer[0 .. got]; + + auto img = new TrueColorImage (width, height, slice); + + browserHandle.runOnWebView((wv) { + wv.favicon = img; + }); + } + } + } + + if(url.length) { + auto callback = new Thing(browser); + + browser.get_host().download_image(&str, true, 16, 0, callback.passable); + } else { + browser.runOnWebView((wv) { + wv.favicon = null; + }); + } + } + + browser.runOnWebView((wv) { + wv.favicon_url = url; + }); } override void on_fullscreen_mode_change(RC!(cef_browser_t) browser, int) { } @@ -450,16 +737,49 @@ version(cef) { } } + class MiniguiFocusHandler : CEF!cef_focus_handler_t { + override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow { + // sdpyPrintDebugString("take"); + } + override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow { + //browser.runOnWebView((ev) { + //sdpyPrintDebugString("setting"); + //ev.parentWindow.focusedWidget = ev; + //}); + + return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen. + // seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent + // even though things work fine if i always cancel except + // it still keeps the decoration assuming focus though even though it doesn't have it which is kinda fucked up but meh + // it also breaks its own pop up menus and drop down boxes to allow this! wtf + } + override void on_got_focus(RC!(cef_browser_t) browser) nothrow { + // sdpyPrintDebugString("got"); + } + } + class MiniguiCefClient : CEF!cef_client_t { + + WebViewWidget_CEF[] listOfWidgets; + + void delegate(scope OpenNewWindowParams) openNewWindow; + MiniguiCefLifeSpanHandler lsh; MiniguiLoadHandler loadHandler; MiniguiDialogHandler dialogHandler; MiniguiDisplayHandler displayHandler; - this() { - lsh = new MiniguiCefLifeSpanHandler(); + MiniguiDownloadHandler downloadHandler; + MiniguiKeyboardHandler keyboardHandler; + MiniguiFocusHandler focusHandler; + this(void delegate(scope OpenNewWindowParams) openNewWindow) { + this.openNewWindow = openNewWindow; + lsh = new MiniguiCefLifeSpanHandler(this); loadHandler = new MiniguiLoadHandler(); dialogHandler = new MiniguiDialogHandler(); displayHandler = new MiniguiDisplayHandler(); + downloadHandler = new MiniguiDownloadHandler(); + keyboardHandler = new MiniguiKeyboardHandler(); + focusHandler = new MiniguiFocusHandler(); } override cef_audio_handler_t* get_audio_handler() { @@ -475,7 +795,7 @@ version(cef) { return displayHandler.returnable; } override cef_download_handler_t* get_download_handler() { - return null; + return downloadHandler.returnable; } override cef_drag_handler_t* get_drag_handler() { return null; @@ -484,7 +804,7 @@ version(cef) { return null; } override cef_focus_handler_t* get_focus_handler() { - return null; + return focusHandler.returnable; } override cef_jsdialog_handler_t* get_jsdialog_handler() { // needed for alert etc. @@ -492,7 +812,7 @@ version(cef) { } override cef_keyboard_handler_t* get_keyboard_handler() { // this can handle keyboard shortcuts etc - return null; + return keyboardHandler.returnable; } override cef_life_span_handler_t* get_life_span_handler() { return lsh.returnable; @@ -520,3 +840,19 @@ version(cef) { } } + +class BrowserClosedEvent : Event { + enum EventString = "browserclosed"; + + this(Widget target) { super(EventString, target); } + override bool cancelable() const { return false; } +} + +/+ +pragma(mangle, "_ZN12CefWindowX115FocusEv") +//pragma(mangle, "_ZN3x116XProto13SetInputFocusERKNS_20SetInputFocusRequestE") +extern(C++) +export void _ZN12CefWindowX115FocusEv() { + sdpyPrintDebugString("OVERRIDDEN"); +} ++/ diff --git a/webview.d b/webview.d index 34591f4..8ee11f3 100644 --- a/webview.d +++ b/webview.d @@ -1061,12 +1061,12 @@ abstract class CEF(Base) { } private Inner inner; - this() { + this() nothrow { if(!__ctfe) construct(); } // ONLY call this if you did a ctfe construction - void construct() { + void construct() nothrow { assert(inner.c.base.size == 0); import core.memory; @@ -1234,6 +1234,7 @@ struct RC(Base) { if(inner is null) return; inner.base.release(&inner.base); inner = null; + //sdpyPrintDebugString("omg release"); } bool opCast(T:bool)() nothrow { return inner !is null; From e14b651266b3ad312d9e6d4275c0149d156cbdf4 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 5 Apr 2022 19:20:13 -0400 Subject: [PATCH 08/11] unreachable code considered fine --- simpledisplay.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simpledisplay.d b/simpledisplay.d index c9eff53..5387559 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -6797,8 +6797,8 @@ struct SyntheticInput { input.mi.dwFlags = MOUSEEVENTF_WHEEL; input.mi.mouseData = button == MouseButton.wheelUp ? 120 : -120; break; - case MouseButton.backButton: throw new NotYetImplementedException(); break; - case MouseButton.forwardButton: throw new NotYetImplementedException(); break; + case MouseButton.backButton: throw new NotYetImplementedException(); + case MouseButton.forwardButton: throw new NotYetImplementedException(); default: } From 77c11d27a5bdf9afa5322ac6d374da7367af46d5 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 6 Apr 2022 08:17:17 -0400 Subject: [PATCH 09/11] lets encrypt workaround on certain openssl versions on windows --- http2.d | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/http2.d b/http2.d index 9d2cc42..65c6522 100644 --- a/http2.d +++ b/http2.d @@ -3069,7 +3069,11 @@ version(use_openssl) { if(ossllib_handle is null) ossllib_handle = dlopen("libssl.so", RTLD_NOW); } else version(Windows) { + //ossllib_handle = LoadLibraryW("libssl-1_1-x64.dll"w.ptr); + //if(ossllib_handle is null) ossllib_handle = LoadLibraryW("libssl32.dll"w.ptr); + //oeaylib_handle = LoadLibraryW("libcrypto-1_1-x64.dll"w.ptr); + //if(oeaylib_handle) oeaylib_handle = LoadLibraryW("libeay32.dll"w.ptr); if(ossllib_handle is null) { @@ -4804,16 +4808,34 @@ version(Windows) { PCCERT_CONTEXT CertEnumCertificatesInStore(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); void loadCertificatesFromRegistry(SSL_CTX* ctx) { - auto store = CertOpenSystemStore(0, "ROOT"); - if(store is null) + if(store is null) { + // import std.stdio; writeln("failed"); return; + } scope(exit) CertCloseStore(store, 0); X509_STORE* ssl_store = SSL_CTX_get_cert_store(ctx); PCCERT_CONTEXT c; while((c = CertEnumCertificatesInStore(store, c)) !is null) { + FILETIME na = c.pCertInfo.NotAfter; + SYSTEMTIME st; + FileTimeToSystemTime(&na, &st); + + /+ + _CRYPTOAPI_BLOB i = cast() c.pCertInfo.Issuer; + + char[256] buffer; + auto p = CertNameToStrA(X509_ASN_ENCODING, &i, CERT_SIMPLE_NAME_STR, buffer.ptr, cast(int) buffer.length); + import std.stdio; writeln(buffer[0 .. p]); + +/ + + if(st.wYear <= 2021) { + // see: https://www.openssl.org/blog/blog/2021/09/13/LetsEncryptRootCertExpire/ + continue; // no point keeping an expired root cert and it can break Let's Encrypt anyway + } + const(ubyte)* thing = c.pbCertEncoded; auto x509 = d2i_X509(null, &thing, c.cbCertEncoded); if (x509) { From 2fb96cc5074fca20db89611e0e1ecc7c71e8c883 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Wed, 6 Apr 2022 11:57:01 -0400 Subject: [PATCH 10/11] use the other common name for newer openssl --- http2.d | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/http2.d b/http2.d index 65c6522..4833243 100644 --- a/http2.d +++ b/http2.d @@ -3069,12 +3069,14 @@ version(use_openssl) { if(ossllib_handle is null) ossllib_handle = dlopen("libssl.so", RTLD_NOW); } else version(Windows) { - //ossllib_handle = LoadLibraryW("libssl-1_1-x64.dll"w.ptr); - //if(ossllib_handle is null) - ossllib_handle = LoadLibraryW("libssl32.dll"w.ptr); - //oeaylib_handle = LoadLibraryW("libcrypto-1_1-x64.dll"w.ptr); - //if(oeaylib_handle) - oeaylib_handle = LoadLibraryW("libeay32.dll"w.ptr); + version(X86_64) + ossllib_handle = LoadLibraryW("libssl-1_1-x64.dll"w.ptr); + if(ossllib_handle is null) + ossllib_handle = LoadLibraryW("libssl32.dll"w.ptr); + version(X86_64) + oeaylib_handle = LoadLibraryW("libcrypto-1_1-x64.dll"w.ptr); + if(oeaylib_handle is null) + oeaylib_handle = LoadLibraryW("libeay32.dll"w.ptr); if(ossllib_handle is null) { ossllib_handle = LoadLibraryW("ssleay32.dll"w.ptr); From 2ee9dd6b4342843684ea56c35519c277d6ff18d5 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Thu, 7 Apr 2022 10:16:26 -0400 Subject: [PATCH 11/11] draw experimentation --- minigui.d | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/minigui.d b/minigui.d index d13aea8..e4c42f4 100644 --- a/minigui.d +++ b/minigui.d @@ -468,6 +468,15 @@ version(Windows) { +/ class Widget : ReflectableProperties { + private bool willDraw() { + return true; + } + + private bool skipPaintCycle() { + return false; + } + + /+ /++ Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need. @@ -1783,6 +1792,9 @@ class Widget : ReflectableProperties { if(hidden) return; + if(skipPaintCycle()) + return; + int paintX = x; int paintY = y; if(this.useNativeDrawing()) { @@ -6413,6 +6425,11 @@ abstract class Layout : Widget { tabStop = false; super(parent); } + + version(none) + override bool willDraw() { + return false; + } } /++ @@ -7695,8 +7712,6 @@ class Window : Widget { super(p); } - - private void actualRedraw() { if(recomputeChildLayoutRequired) recomputeChildLayoutEntry(); @@ -7719,7 +7734,7 @@ class Window : Widget { ugh = ugh.parent; } auto painter = w.draw(true); - privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, true); + privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw()); } @@ -10874,6 +10889,9 @@ class Button : MouseActivatedWidget { override int heightStretchiness() { return 3; } override int widthStretchiness() { return 3; } + version(none) + override bool skipPaintCycle() { return true; } + /++ If true, this button will emit trigger events on double (and other quick events, if added) click events as well as on normal single click events.