From 1fc4f4263c8b5688635da3e112c01f49f146f88e Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 11 Apr 2020 09:57:22 -0400 Subject: [PATCH] stuff --- color.d | 27 +++++++++- simpledisplay.d | 134 ++++++++++++++++++++++++++++++++++++++++++++---- terminal.d | 29 ++++++++++- 3 files changed, 178 insertions(+), 12 deletions(-) diff --git a/color.d b/color.d index bc08b63..74558d7 100644 --- a/color.d +++ b/color.d @@ -258,7 +258,12 @@ struct Color { throw new Exception("Unknown color " ~ s); } - /// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa + /++ + Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa + + History: + The short-form hex string parsing (`#fff`) was added on April 10, 2020. (v7.2.0) + +/ static Color fromString(scope const(char)[] s) { s = s.stripInternal(); @@ -334,6 +339,17 @@ struct Color { if(s.length && s[0] == '#') s = s[1 .. $]; + // support short form #fff for example + if(s.length == 3 || s.length == 4) { + string n; + n.reserve(8); + foreach(ch; s) { + n ~= ch; + n ~= ch; + } + s = n; + } + // not a built in... do it as a hex string if(s.length >= 2) { c.r = fromHexInternal(s[0 .. 2]); @@ -417,6 +433,15 @@ struct Color { } } +unittest { + Color c = Color.fromString("#fff"); + assert(c == Color.white); + assert(c == Color.fromString("#ffffff")); + + c = Color.fromString("#f0f"); + assert(c == Color.fromString("rgb(255, 0, 255)")); +} + nothrow @safe private string toHexInternal(ubyte b) { string s; diff --git a/simpledisplay.d b/simpledisplay.d index 3896b37..e569668 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -105,6 +105,14 @@ See the examples and topics list below to learn more. + $(WARNING + There should only be one GUI thread per application, + and all windows should be created in it and your + event loop should run there. + + To do otherwise is undefined behavior and has no + cross platform guarantees. + ) $(H2 About this documentation) @@ -1339,6 +1347,8 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { parent = the parent window, if applicable +/ this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) { + claimGuiThread(); + version(sdpy_thread_checks) assert(thisIsGuiThread); this._width = width; this._height = height; this.openglMode = opengl; @@ -2383,7 +2393,7 @@ private: } // wake up event processor - bool eventWakeUp () { + static bool eventWakeUp () { version(X11) { import core.sys.posix.unistd : write; ulong n = 1; @@ -2529,6 +2539,31 @@ private: if (sw is null || sw.closed) continue; sw.processCustomEvents(); } + + // run pending [runInGuiThread] delegates + more: + RunQueueMember* next; + synchronized(runInGuiThreadLock) { + if(runInGuiThreadQueue.length) { + next = runInGuiThreadQueue[0]; + runInGuiThreadQueue = runInGuiThreadQueue[1 .. $]; + } else { + next = null; + } + } + + if(next) { + try { + next.dg(); + next.thrown = null; + } catch(Throwable t) { + next.thrown = t; + } + + next.signal.notify(); + + goto more; + } } // 0: infinite (i.e. no scheduled events in queue) @@ -2704,18 +2739,24 @@ struct EventLoop { return EventLoop(0, null); } + __gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi + /// Construct an application-global event loop for yourself /// See_Also: [SimpleWindow.setEventHandlers] this(long pulseTimeout, void delegate() handlePulse) { - if(impl is null) - impl = new EventLoopImpl(pulseTimeout, handlePulse); - else { - if(pulseTimeout) { - impl.pulseTimeout = pulseTimeout; - impl.handlePulse = handlePulse; + synchronized(monitor) { + if(impl is null) { + claimGuiThread(); + version(sdpy_thread_checks) assert(thisIsGuiThread); + impl = new EventLoopImpl(pulseTimeout, handlePulse); + } else { + if(pulseTimeout) { + impl.pulseTimeout = pulseTimeout; + impl.handlePulse = handlePulse; + } } + impl.refcount++; } - impl.refcount++; } ~this() { @@ -2752,7 +2793,7 @@ struct EventLoop { return impl.signalHandler; } - static EventLoopImpl* impl; + __gshared static EventLoopImpl* impl; } version(linux) @@ -6993,6 +7034,81 @@ void flushGui() { } } +/++ + Runs the given code in the GUI thread when its event loop + is available, blocking until it completes. This allows you + to create and manipulate windows from another thread without + invoking undefined behavior. + + If this is the gui thread, it runs the code immediately. + + If no gui thread exists yet, the current thread is assumed + to be it. Attempting to create windows or run the event loop + in any other thread will cause an assertion failure. + + + $(TIP + Did you know you can use UFCS on delegate literals? + + () { + // code here + }.runInGuiThread; + ) + + History: + Added April 10, 2020 (v7.2.0) ++/ +void runInGuiThread(scope void delegate() dg) @trusted { + claimGuiThread(); + + if(thisIsGuiThread) { + dg(); + return; + } + + import core.sync.semaphore; + static Semaphore sc; + if(sc is null) + sc = new Semaphore(); + + static RunQueueMember* rqm; + if(rqm is null) + rqm = new RunQueueMember; + rqm.dg = cast(typeof(rqm.dg)) dg; + rqm.signal = sc; + rqm.thrown = null; + + synchronized(runInGuiThreadLock) { + runInGuiThreadQueue ~= rqm; + } + + if(!SimpleWindow.eventWakeUp()) + throw new Error("runInGuiThread impossible; eventWakeUp failed"); + + rqm.signal.wait(); + + if(rqm.thrown) + throw rqm.thrown; +} + +private void claimGuiThread() { + import core.atomic; + if(cas(&guiThreadExists, false, true)) + thisIsGuiThread = true; +} + +private struct RunQueueMember { + void delegate() dg; + import core.sync.semaphore; + Semaphore signal; + Throwable thrown; +} + +private __gshared RunQueueMember*[] runInGuiThreadQueue; +private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE +private bool thisIsGuiThread = false; +private shared bool guiThreadExists = false; + /// Used internal to dispatch events to various classes. interface CapableOfHandlingNativeEvent { NativeEventHandler getNativeEventHandler(); diff --git a/terminal.d b/terminal.d index f9fe786..6a056c0 100644 --- a/terminal.d +++ b/terminal.d @@ -1280,6 +1280,9 @@ struct Terminal { writeln("\n\n"); setTitle(tew.terminalEmulator.currentTitle ~ " "); tew.term = null; + + if(integratedTerminalEmulatorConfiguration.closeOnExit) + tew.parentWindow.close(); } else if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) { writeStringRaw("\033[23;0t"); // restore window title from the stack @@ -5866,7 +5869,17 @@ int approximate16Color(RGB color) { version(TerminalDirectToEmulator) { - /// + /++ + Indicates the TerminalDirectToEmulator features + are present. You can check this with `static if`. + + $(WARNING + This will cause the [Terminal] constructor to spawn a GUI thread with [arsd.minigui]/[arsd.simpledisplay]. + + This means you can NOT use those libraries in your + own thing without using the [arsd.simpledisplay.runInGuiThread] helper since otherwise the main thread is inaccessible, since having two different threads creating event loops or windows is undefined behavior with those libraries. + ) + +/ enum IntegratedEmulator = true; /++ @@ -5902,11 +5915,23 @@ version(TerminalDirectToEmulator) { /// ditto int fontSize = 14; - /// Requested initial terminal size in character cells. You may not actually get exactly this. + /++ + Requested initial terminal size in character cells. You may not actually get exactly this. + +/ int initialWidth = 80; /// ditto int initialHeight = 40; + /++ + If `true`, the window will close automatically when the main thread exits. + Otherwise, the window will remain open so the user can work with output before + it disappears. + + History: + Added April 10, 2020 (v7.2.0) + +/ + bool closeOnExit = false; + /++ Gives you a chance to modify the window as it is constructed. Intended to let you add custom menu options.