sdpy integration a lil easier

This commit is contained in:
Adam D. Ruppe 2020-12-29 22:12:10 -05:00
parent 49a04b764a
commit 2adf9729b5
1 changed files with 157 additions and 14 deletions

View File

@ -61,6 +61,11 @@
* Do a full TUI widget set. I might do some basics and lay a little groundwork, but a full TUI
is outside the scope of this module (unless I can do it really small.)
)
History:
On December 29, 2020 the structs and their destructors got more protection against in-GC finalization errors and duplicate executions.
This should not affect your code.
+/
module arsd.terminal;
@ -1108,6 +1113,7 @@ struct Terminal {
/++
+/
this(ConsoleOutputType type) {
_initialized = true;
createLock();
this.type = type;
@ -1204,6 +1210,7 @@ struct Terminal {
* ditto on getSizeOverride. That's there so you can do something instead of ioctl.
*/
this(ConsoleOutputType type, int fdIn = 0, int fdOut = 1, int[] delegate() getSizeOverride = null) {
_initialized = true;
createLock();
posixInitialize(type, fdIn, fdOut, getSizeOverride);
}
@ -1258,6 +1265,7 @@ struct Terminal {
version(Win32Console)
/// ditto
this(ConsoleOutputType type) {
_initialized = true;
createLock();
if(UseVtSequences) {
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
@ -1322,9 +1330,19 @@ struct Terminal {
}
// only use this if you are sure you know what you want, since the terminal is a shared resource you generally really want to reset it to normal when you leave...
bool _suppressDestruction;
bool _suppressDestruction = false;
bool _initialized = false; // set to true for Terminal.init purposes, but ctors will set it to false initially, then might reset to true if needed
~this() {
if(!_initialized)
return;
import core.memory;
static if(is(typeof(GC.inFinalizer)))
if(GC.inFinalizer)
return;
if(_suppressDestruction) {
flush();
return;
@ -2289,10 +2307,11 @@ struct RealTimeConsoleInput {
private ConsoleInputFlags flags;
private Terminal* terminal;
private void delegate()[] destructor;
private void function(RealTimeConsoleInput*)[] destructor;
/// To capture input, you need to provide a terminal and some flags.
public this(Terminal* terminal, ConsoleInputFlags flags) {
_initialized = true;
this.flags = flags;
this.terminal = terminal;
@ -2316,7 +2335,7 @@ struct RealTimeConsoleInput {
// if(flags & ConsoleInputFlags.raw) // FIXME: maybe that should be a separate flag for ENABLE_LINE_INPUT
SetConsoleMode(inputHandle, mode);
destructor ~= { SetConsoleMode(inputHandle, oldInput); };
destructor ~= (this_) { SetConsoleMode(this_.inputHandle, this_.oldInput); };
GetConsoleMode(terminal.hConsole, &oldOutput);
@ -2326,7 +2345,7 @@ struct RealTimeConsoleInput {
if(!(flags & ConsoleInputFlags.noEolWrap))
mode |= ENABLE_WRAP_AT_EOL_OUTPUT; /* 0x02 */
SetConsoleMode(terminal.hConsole, mode);
destructor ~= { SetConsoleMode(terminal.hConsole, oldOutput); };
destructor ~= (this_) { SetConsoleMode(this_.terminal.hConsole, this_.oldOutput); };
}
version(TerminalDirectToEmulator) {
@ -2344,7 +2363,7 @@ struct RealTimeConsoleInput {
if(flags & ConsoleInputFlags.selectiveMouse) {
// arsd terminal extension, but harmless on most other terminals
terminal.writeStringRaw("\033[?1014h");
destructor ~= { terminal.writeStringRaw("\033[?1014l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?1014l"); };
} else if(flags & ConsoleInputFlags.mouse) {
// basic button press+release notification
@ -2352,7 +2371,7 @@ struct RealTimeConsoleInput {
// right now this works well on xterm but rxvt isn't sending movements...
terminal.writeStringRaw("\033[?1000h");
destructor ~= { terminal.writeStringRaw("\033[?1000l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?1000l"); };
// the MOUSE_HACK env var is for the case where I run screen
// but set TERM=xterm (which I do from putty). The 1003 mouse mode
// doesn't work there, breaking mouse support entirely. So by setting
@ -2362,22 +2381,22 @@ struct RealTimeConsoleInput {
if(terminal.terminalInFamily("xterm") && environment.get("MOUSE_HACK") != "1002") {
// this is vt200 mouse with full motion tracking, supported by xterm
terminal.writeStringRaw("\033[?1003h");
destructor ~= { terminal.writeStringRaw("\033[?1003l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?1003l"); };
} else if(terminal.terminalInFamily("rxvt", "screen", "tmux") || environment.get("MOUSE_HACK") == "1002") {
terminal.writeStringRaw("\033[?1002h"); // this is vt200 mouse with press/release and motion notification iff buttons are pressed
destructor ~= { terminal.writeStringRaw("\033[?1002l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?1002l"); };
}
}
if(flags & ConsoleInputFlags.paste) {
if(terminal.terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
terminal.writeStringRaw("\033[?2004h"); // bracketed paste mode
destructor ~= { terminal.writeStringRaw("\033[?2004l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?2004l"); };
}
}
if(terminal.tcaps & TerminalCapabilities.arsdHyperlinks) {
terminal.writeStringRaw("\033[?3004h"); // bracketed link mode
destructor ~= { terminal.writeStringRaw("\033[?3004l"); };
destructor ~= (this_) { this_.terminal.writeStringRaw("\033[?3004l"); };
}
// try to ensure the terminal is in UTF-8 mode
@ -2402,10 +2421,10 @@ struct RealTimeConsoleInput {
if(listenTo != -1) {
addFileEventListeners(listenTo, &eventListener, null, null);
destructor ~= { removeFileEventListeners(listenTo); };
destructor ~= (this_) { this_.removeFileEventListeners(listenTo); };
}
addOnIdle(&terminal.flush);
destructor ~= { removeOnIdle(&terminal.flush); };
destructor ~= (this_) { this_.removeOnIdle(&terminal.flush); };
}
}
@ -2477,7 +2496,7 @@ struct RealTimeConsoleInput {
that in a future version, but it doesn't hurt to call it twice anyway, so I recommend
calling flush yourself in any code you write using this.
+/
void integrateWithSimpleDisplayEventLoop()(void delegate(InputEvent) userEventHandler) {
auto integrateWithSimpleDisplayEventLoop()(void delegate(InputEvent) userEventHandler) {
this.userEventHandler = userEventHandler;
import arsd.simpledisplay;
version(Win32Console)
@ -2485,6 +2504,8 @@ struct RealTimeConsoleInput {
else version(linux)
auto listener = new PosixFdReader(&fdReadyReader, fdIn);
else static assert(0, "sdpy event loop integration not implemented on this platform");
return listener;
}
version(with_eventloop) {
@ -2511,8 +2532,16 @@ struct RealTimeConsoleInput {
}
bool _suppressDestruction;
bool _initialized = false;
~this() {
if(!_initialized)
return;
import core.memory;
static if(is(typeof(GC.inFinalizer)))
if(GC.inFinalizer)
return;
if(_suppressDestruction)
return;
@ -2539,7 +2568,7 @@ struct RealTimeConsoleInput {
// we're just undoing everything the constructor did, in reverse order, same criteria
foreach_reverse(d; destructor)
d();
d(&this);
}
/**
@ -7797,6 +7826,120 @@ private version(Windows) {
}
}
/++
Convenience object to forward terminal keys to a [arsd.simpledisplay.SimpleWindow]. Meant for cases when you have a gui window as the primary mode of interaction, but also want keys to the parent terminal to be usable too by the window.
Please note that not all keys may be accurately forwarded. It is not meant to be 100% comprehensive; that's for the window.
History:
Added December 29, 2020.
+/
auto SdpyIntegratedKeys(SimpleWindow)(SimpleWindow window) {
struct impl {
static import sdpy = arsd.simpledisplay;
Terminal* terminal;
RealTimeConsoleInput* rtti;
typeof(RealTimeConsoleInput.init.integrateWithSimpleDisplayEventLoop(null)) listener;
this(sdpy.SimpleWindow window) {
terminal = new Terminal(ConsoleOutputType.linear);
rtti = new RealTimeConsoleInput(terminal, ConsoleInputFlags.releasedKeys);
listener = rtti.integrateWithSimpleDisplayEventLoop(delegate(InputEvent ie) {
if(ie.type != InputEvent.Type.KeyboardEvent)
return;
auto kbd = ie.get!(InputEvent.Type.KeyboardEvent);
if(window.handleKeyEvent !is null) {
sdpy.KeyEvent ke;
ke.pressed = kbd.pressed;
if(kbd.modifierState & ModifierState.control)
ke.modifierState |= sdpy.ModifierState.ctrl;
if(kbd.modifierState & ModifierState.alt)
ke.modifierState |= sdpy.ModifierState.alt;
if(kbd.modifierState & ModifierState.shift)
ke.modifierState |= sdpy.ModifierState.shift;
sw: switch(kbd.which) {
case KeyboardEvent.Key.escape: ke.key = sdpy.Key.Escape; break;
case KeyboardEvent.Key.F1: ke.key = sdpy.Key.F1; break;
case KeyboardEvent.Key.F2: ke.key = sdpy.Key.F2; break;
case KeyboardEvent.Key.F3: ke.key = sdpy.Key.F3; break;
case KeyboardEvent.Key.F4: ke.key = sdpy.Key.F4; break;
case KeyboardEvent.Key.F5: ke.key = sdpy.Key.F5; break;
case KeyboardEvent.Key.F6: ke.key = sdpy.Key.F6; break;
case KeyboardEvent.Key.F7: ke.key = sdpy.Key.F7; break;
case KeyboardEvent.Key.F8: ke.key = sdpy.Key.F8; break;
case KeyboardEvent.Key.F9: ke.key = sdpy.Key.F9; break;
case KeyboardEvent.Key.F10: ke.key = sdpy.Key.F10; break;
case KeyboardEvent.Key.F11: ke.key = sdpy.Key.F11; break;
case KeyboardEvent.Key.F12: ke.key = sdpy.Key.F12; break;
case KeyboardEvent.Key.LeftArrow: ke.key = sdpy.Key.Left; break;
case KeyboardEvent.Key.RightArrow: ke.key = sdpy.Key.Right; break;
case KeyboardEvent.Key.UpArrow: ke.key = sdpy.Key.Up; break;
case KeyboardEvent.Key.DownArrow: ke.key = sdpy.Key.Down; break;
case KeyboardEvent.Key.Insert: ke.key = sdpy.Key.Insert; break;
case KeyboardEvent.Key.Delete: ke.key = sdpy.Key.Delete; break;
case KeyboardEvent.Key.Home: ke.key = sdpy.Key.Home; break;
case KeyboardEvent.Key.End: ke.key = sdpy.Key.End; break;
case KeyboardEvent.Key.PageUp: ke.key = sdpy.Key.PageUp; break;
case KeyboardEvent.Key.PageDown: ke.key = sdpy.Key.PageDown; break;
case KeyboardEvent.Key.ScrollLock: ke.key = sdpy.Key.ScrollLock; break;
case '\r', '\n': ke.key = sdpy.Key.Enter; break;
case '\t': ke.key = sdpy.Key.Tab; break;
case ' ': ke.key = sdpy.Key.Space; break;
case '\b': ke.key = sdpy.Key.Backspace; break;
case '`': ke.key = sdpy.Key.Grave; break;
case '-': ke.key = sdpy.Key.Dash; break;
case '=': ke.key = sdpy.Key.Equals; break;
case '[': ke.key = sdpy.Key.LeftBracket; break;
case ']': ke.key = sdpy.Key.RightBracket; break;
case '\\': ke.key = sdpy.Key.Backslash; break;
case ';': ke.key = sdpy.Key.Semicolon; break;
case '\'': ke.key = sdpy.Key.Apostrophe; break;
case ',': ke.key = sdpy.Key.Comma; break;
case '.': ke.key = sdpy.Key.Period; break;
case '/': ke.key = sdpy.Key.Slash; break;
static foreach(ch; 'A' .. ('Z' + 1)) {
case ch, ch + 32:
version(Windows)
ke.key = cast(sdpy.Key) ch;
else
ke.key = cast(sdpy.Key) (ch + 32);
break sw;
}
static foreach(ch; '0' .. ('9' + 1)) {
case ch:
ke.key = cast(sdpy.Key) ch;
break sw;
}
default:
}
// I'm tempted to leave the window null since it didn't originate from here
// or maybe set a ModifierState....
//ke.window = window;
window.handleKeyEvent(ke);
}
if(window.handleCharEvent !is null) {
if(kbd.isCharacter)
window.handleCharEvent(kbd.which);
}
});
}
~this() {
listener.dispose();
.destroy(*rtti);
.destroy(*terminal);
rtti = null;
terminal = null;
}
}
return impl(window);
}
/*
ONLY SUPPORTED ON MY TERMINAL EMULATOR IN GENERAL