mirror of https://github.com/adamdruppe/arsd.git
sdpy integration a lil easier
This commit is contained in:
parent
49a04b764a
commit
2adf9729b5
171
terminal.d
171
terminal.d
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue