mirror of https://github.com/adamdruppe/arsd.git
resize and ctrl+c signal handling
This commit is contained in:
parent
1e94af20d8
commit
05172d7d91
162
terminal.d
162
terminal.d
|
@ -6,6 +6,13 @@
|
||||||
* this struct will perform console initialization; when the struct
|
* this struct will perform console initialization; when the struct
|
||||||
* goes out of scope, any changes in console settings will be automatically
|
* goes out of scope, any changes in console settings will be automatically
|
||||||
* reverted.
|
* reverted.
|
||||||
|
*
|
||||||
|
* Note: on Posix, it traps SIGINT and translates it into an input event. You should
|
||||||
|
* keep your event loop moving and keep an eye open for this to exit cleanly; simply break
|
||||||
|
* your event loop upon receiving a UserInterruptionEvent. (Without
|
||||||
|
* the signal handler, ctrl+c can leave your terminal in a bizarre state.)
|
||||||
|
*
|
||||||
|
* As a user, if you have to forcibly kill your program and the event doesn't work, there's still ctrl+\
|
||||||
*/
|
*/
|
||||||
module terminal;
|
module terminal;
|
||||||
|
|
||||||
|
@ -14,6 +21,19 @@ module terminal;
|
||||||
version(linux)
|
version(linux)
|
||||||
enum SIGWINCH = 28; // FIXME: confirm this is correct on other posix
|
enum SIGWINCH = 28; // FIXME: confirm this is correct on other posix
|
||||||
|
|
||||||
|
version(Posix) {
|
||||||
|
__gshared bool windowSizeChanged = false;
|
||||||
|
__gshared bool interrupted = false;
|
||||||
|
extern(C)
|
||||||
|
void sizeSignalHandler(int sigNumber) {
|
||||||
|
windowSizeChanged = true;
|
||||||
|
}
|
||||||
|
extern(C)
|
||||||
|
void interruptSignalHandler(int sigNumber) {
|
||||||
|
interrupted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parts of this were taken from Robik's ConsoleD
|
// parts of this were taken from Robik's ConsoleD
|
||||||
// https://github.com/robik/ConsoleD/blob/master/consoled.d
|
// https://github.com/robik/ConsoleD/blob/master/consoled.d
|
||||||
|
|
||||||
|
@ -214,12 +234,16 @@ enum Color : ushort {
|
||||||
/// When capturing input, what events are you interested in?
|
/// When capturing input, what events are you interested in?
|
||||||
///
|
///
|
||||||
/// Note: these flags can be OR'd together to select more than one option at a time.
|
/// Note: these flags can be OR'd together to select more than one option at a time.
|
||||||
|
///
|
||||||
|
/// Ctrl+C and other keyboard input is always captured, though it may be line buffered if you don't use raw.
|
||||||
enum ConsoleInputFlags {
|
enum ConsoleInputFlags {
|
||||||
raw = 0, /// raw input returns keystrokes immediately, without line buffering
|
raw = 0, /// raw input returns keystrokes immediately, without line buffering
|
||||||
echo = 1, /// do you want to automatically echo input back to the user?
|
echo = 1, /// do you want to automatically echo input back to the user?
|
||||||
mouse = 2, /// capture mouse events
|
mouse = 2, /// capture mouse events
|
||||||
paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
|
paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes)
|
||||||
size = 8, /// window resize events
|
size = 8, /// window resize events
|
||||||
|
|
||||||
|
allInputEvents = 8|4|2, /// subscribe to all input events.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how terminal output should be handled.
|
/// Defines how terminal output should be handled.
|
||||||
|
@ -567,6 +591,9 @@ struct Terminal {
|
||||||
foreground ^= LowContrast;
|
foreground ^= LowContrast;
|
||||||
background ^= LowContrast;
|
background ^= LowContrast;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// FIXME: is this if good?
|
||||||
|
//if(force == ForceOption.alwaysSend || foreground != _currentForeground || background != _currentBackground) {
|
||||||
SetConsoleTextAttribute(
|
SetConsoleTextAttribute(
|
||||||
GetStdHandle(STD_OUTPUT_HANDLE),
|
GetStdHandle(STD_OUTPUT_HANDLE),
|
||||||
cast(ushort)((background << 4) | foreground));
|
cast(ushort)((background << 4) | foreground));
|
||||||
|
@ -815,7 +842,8 @@ struct Terminal {
|
||||||
|
|
||||||
private string writeBuffer;
|
private string writeBuffer;
|
||||||
|
|
||||||
private void writeStringRaw(in char[] s) {
|
// you really, really shouldn't use this unless you know what you are doing
|
||||||
|
/*private*/ void writeStringRaw(in char[] s) {
|
||||||
// FIXME: make sure all the data is sent, check for errors
|
// FIXME: make sure all the data is sent, check for errors
|
||||||
version(Posix) {
|
version(Posix) {
|
||||||
writeBuffer ~= s; // buffer it to do everything at once in flush() calls
|
writeBuffer ~= s; // buffer it to do everything at once in flush() calls
|
||||||
|
@ -874,6 +902,8 @@ struct RealTimeConsoleInput {
|
||||||
|
|
||||||
version(Posix) {
|
version(Posix) {
|
||||||
private int fd;
|
private int fd;
|
||||||
|
private sigaction_t oldSigWinch;
|
||||||
|
private sigaction_t oldSigIntr;
|
||||||
private termios old;
|
private termios old;
|
||||||
ubyte[128] hack;
|
ubyte[128] hack;
|
||||||
// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
|
// apparently termios isn't the size druntime thinks it is (at least on 32 bit, sometimes)....
|
||||||
|
@ -928,34 +958,40 @@ struct RealTimeConsoleInput {
|
||||||
|
|
||||||
version(Posix) {
|
version(Posix) {
|
||||||
this.fd = 0; // stdin
|
this.fd = 0; // stdin
|
||||||
tcgetattr(fd, &old);
|
|
||||||
auto n = old;
|
|
||||||
|
|
||||||
auto f = ICANON;
|
{
|
||||||
if(!(flags & ConsoleInputFlags.echo))
|
tcgetattr(fd, &old);
|
||||||
f |= ECHO;
|
auto n = old;
|
||||||
|
|
||||||
n.c_lflag &= ~f;
|
auto f = ICANON;
|
||||||
tcsetattr(fd, TCSANOW, &n);
|
if(!(flags & ConsoleInputFlags.echo))
|
||||||
|
f |= ECHO;
|
||||||
|
|
||||||
|
n.c_lflag &= ~f;
|
||||||
|
tcsetattr(fd, TCSANOW, &n);
|
||||||
|
}
|
||||||
|
|
||||||
// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
|
// some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3
|
||||||
//destructor ~= { tcsetattr(fd, TCSANOW, &old); };
|
//destructor ~= { tcsetattr(fd, TCSANOW, &old); };
|
||||||
|
|
||||||
/+
|
|
||||||
if(flags & ConsoleInputFlags.size) {
|
if(flags & ConsoleInputFlags.size) {
|
||||||
// FIXME: finish this
|
|
||||||
import core.sys.posix.signal;
|
import core.sys.posix.signal;
|
||||||
sigaction n;
|
sigaction_t n;
|
||||||
n.sa_handler = &handler;
|
n.sa_handler = &sizeSignalHandler;
|
||||||
n.sa_mask = 0;
|
n.sa_mask = cast(sigset_t) 0;
|
||||||
n.sa_flags = 0;
|
n.sa_flags = 0;
|
||||||
sigaction o;
|
sigaction(SIGWINCH, &n, &oldSigWinch);
|
||||||
sigaction(SIGWINCH, &n, &o);
|
|
||||||
|
|
||||||
// restoration
|
|
||||||
sigaction(SIGWINCH, &o, null);
|
|
||||||
}
|
}
|
||||||
+/
|
|
||||||
|
{
|
||||||
|
import core.sys.posix.signal;
|
||||||
|
sigaction_t n;
|
||||||
|
n.sa_handler = &interruptSignalHandler;
|
||||||
|
n.sa_mask = cast(sigset_t) 0;
|
||||||
|
n.sa_flags = 0;
|
||||||
|
sigaction(SIGINT, &n, &oldSigIntr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(flags & ConsoleInputFlags.mouse) {
|
if(flags & ConsoleInputFlags.mouse) {
|
||||||
if(terminal.terminalInFamily("xterm", "rxvt", "screen", "linux")) {
|
if(terminal.terminalInFamily("xterm", "rxvt", "screen", "linux")) {
|
||||||
|
@ -1007,6 +1043,15 @@ struct RealTimeConsoleInput {
|
||||||
// the delegate thing doesn't actually work for this... for some reason
|
// the delegate thing doesn't actually work for this... for some reason
|
||||||
version(Posix)
|
version(Posix)
|
||||||
tcsetattr(fd, TCSANOW, &old);
|
tcsetattr(fd, TCSANOW, &old);
|
||||||
|
|
||||||
|
version(Posix) {
|
||||||
|
if(flags & ConsoleInputFlags.size) {
|
||||||
|
// restoration
|
||||||
|
sigaction(SIGWINCH, &oldSigWinch, null);
|
||||||
|
}
|
||||||
|
sigaction(SIGINT, &oldSigIntr, null);
|
||||||
|
}
|
||||||
|
|
||||||
// we're just undoing everything the constructor did, in reverse order, same criteria
|
// we're just undoing everything the constructor did, in reverse order, same criteria
|
||||||
foreach_reverse(d; destructor)
|
foreach_reverse(d; destructor)
|
||||||
d();
|
d();
|
||||||
|
@ -1048,13 +1093,23 @@ struct RealTimeConsoleInput {
|
||||||
//char[128] inputBuffer;
|
//char[128] inputBuffer;
|
||||||
//int inputBufferPosition;
|
//int inputBufferPosition;
|
||||||
version(Posix)
|
version(Posix)
|
||||||
int nextRaw() {
|
int nextRaw(bool interruptable = false) {
|
||||||
char[1] buf;
|
char[1] buf;
|
||||||
|
try_again:
|
||||||
auto ret = read(fd, buf.ptr, buf.length);
|
auto ret = read(fd, buf.ptr, buf.length);
|
||||||
if(ret == 0)
|
if(ret == 0)
|
||||||
return 0; // input closed
|
return 0; // input closed
|
||||||
if(ret == -1)
|
if(ret == -1) {
|
||||||
throw new Exception("read failed");
|
import core.stdc.errno;
|
||||||
|
if(errno == EINTR)
|
||||||
|
// interrupted by signal call, quite possibly resize or ctrl+c which we want to check for in the event loop
|
||||||
|
if(interruptable)
|
||||||
|
return -1;
|
||||||
|
else
|
||||||
|
goto try_again;
|
||||||
|
else
|
||||||
|
throw new Exception("read failed");
|
||||||
|
}
|
||||||
|
|
||||||
//terminal.writef("RAW READ: %d\n", buf[0]);
|
//terminal.writef("RAW READ: %d\n", buf[0]);
|
||||||
|
|
||||||
|
@ -1108,9 +1163,23 @@ struct RealTimeConsoleInput {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_more:
|
||||||
|
if(interrupted) {
|
||||||
|
return InputEvent(UserInterruptionEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(windowSizeChanged) {
|
||||||
|
auto oldWidth = terminal.width;
|
||||||
|
auto oldHeight = terminal.height;
|
||||||
|
terminal.updateSize();
|
||||||
|
windowSizeChanged = false;
|
||||||
|
return InputEvent(SizeChangedEvent(oldWidth, oldHeight, terminal.width, terminal.height));
|
||||||
|
}
|
||||||
|
|
||||||
auto more = readNextEvents();
|
auto more = readNextEvents();
|
||||||
while(!more.length)
|
if(!more.length)
|
||||||
more = readNextEvents();
|
goto wait_for_more; // i used to do a loop (readNextEvents can read something, but it might be discarded by the input filter) but now it goto's above because readNextEvents might be interrupted by a SIGWINCH aka size event so we want to check that at least
|
||||||
|
|
||||||
assert(more.length);
|
assert(more.length);
|
||||||
|
|
||||||
auto e = more[0];
|
auto e = more[0];
|
||||||
|
@ -1206,6 +1275,7 @@ struct RealTimeConsoleInput {
|
||||||
auto ev = record.WindowBufferSizeEvent;
|
auto ev = record.WindowBufferSizeEvent;
|
||||||
// FIXME
|
// FIXME
|
||||||
break;
|
break;
|
||||||
|
// FIXME: can we catch ctrl+c here too?
|
||||||
default:
|
default:
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
@ -1398,7 +1468,9 @@ struct RealTimeConsoleInput {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto c = nextRaw();
|
auto c = nextRaw(true);
|
||||||
|
if(c == -1)
|
||||||
|
return null; // interrupted; give back nothing so the other level can recheck signal flags
|
||||||
if(c == '\033') {
|
if(c == '\033') {
|
||||||
if(timedCheckForInput(50)) {
|
if(timedCheckForInput(50)) {
|
||||||
// escape sequence
|
// escape sequence
|
||||||
|
@ -1524,6 +1596,17 @@ struct MouseEvent {
|
||||||
uint modifierState; /// shift, ctrl, alt, meta, altgr
|
uint modifierState; /// shift, ctrl, alt, meta, altgr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// .
|
||||||
|
struct SizeChangedEvent {
|
||||||
|
int oldWidth;
|
||||||
|
int oldHeight;
|
||||||
|
int newWidth;
|
||||||
|
int newHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the user hitting ctrl+c will send this
|
||||||
|
struct UserInterruptionEvent {}
|
||||||
|
|
||||||
interface CustomEvent {}
|
interface CustomEvent {}
|
||||||
|
|
||||||
/// GetNextEvent returns this. Check the type, then use get to get the more detailed input
|
/// GetNextEvent returns this. Check the type, then use get to get the more detailed input
|
||||||
|
@ -1533,8 +1616,10 @@ struct InputEvent {
|
||||||
CharacterEvent, ///.
|
CharacterEvent, ///.
|
||||||
NonCharacterKeyEvent, /// .
|
NonCharacterKeyEvent, /// .
|
||||||
PasteEvent, /// .
|
PasteEvent, /// .
|
||||||
MouseEvent, /// .
|
MouseEvent, /// only sent if you subscribed to mouse events
|
||||||
CustomEvent
|
SizeChangedEvent, /// only sent if you subscribed to size events
|
||||||
|
UserInterruptionEvent, /// the user hit ctrl+c
|
||||||
|
CustomEvent /// .
|
||||||
}
|
}
|
||||||
|
|
||||||
/// .
|
/// .
|
||||||
|
@ -1552,6 +1637,10 @@ struct InputEvent {
|
||||||
return pasteEvent;
|
return pasteEvent;
|
||||||
else static if(T == Type.MouseEvent)
|
else static if(T == Type.MouseEvent)
|
||||||
return mouseEvent;
|
return mouseEvent;
|
||||||
|
else static if(T == Type.SizeChangedEvent)
|
||||||
|
return sizeChangedEvent;
|
||||||
|
else static if(T == Type.UserInterruptionEvent)
|
||||||
|
return userInterruptionEvent;
|
||||||
else static if(T == Type.CustomEvent)
|
else static if(T == Type.CustomEvent)
|
||||||
return customEvent;
|
return customEvent;
|
||||||
else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
|
else static assert(0, "Type " ~ T.stringof ~ " not added to the get function");
|
||||||
|
@ -1574,6 +1663,14 @@ struct InputEvent {
|
||||||
t = Type.MouseEvent;
|
t = Type.MouseEvent;
|
||||||
mouseEvent = c;
|
mouseEvent = c;
|
||||||
}
|
}
|
||||||
|
this(SizeChangedEvent c) {
|
||||||
|
t = Type.SizeChangedEvent;
|
||||||
|
sizeChangedEvent = c;
|
||||||
|
}
|
||||||
|
this(UserInterruptionEvent c) {
|
||||||
|
t = Type.UserInterruptionEvent;
|
||||||
|
userInterruptionEvent = c;
|
||||||
|
}
|
||||||
this(CustomEvent c) {
|
this(CustomEvent c) {
|
||||||
t = Type.CustomEvent;
|
t = Type.CustomEvent;
|
||||||
customEvent = c;
|
customEvent = c;
|
||||||
|
@ -1586,6 +1683,8 @@ struct InputEvent {
|
||||||
NonCharacterKeyEvent nonCharacterKeyEvent;
|
NonCharacterKeyEvent nonCharacterKeyEvent;
|
||||||
PasteEvent pasteEvent;
|
PasteEvent pasteEvent;
|
||||||
MouseEvent mouseEvent;
|
MouseEvent mouseEvent;
|
||||||
|
SizeChangedEvent sizeChangedEvent;
|
||||||
|
UserInterruptionEvent userInterruptionEvent;
|
||||||
CustomEvent customEvent;
|
CustomEvent customEvent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1596,7 +1695,7 @@ void main() {
|
||||||
auto terminal = Terminal(ConsoleOutputType.linear);
|
auto terminal = Terminal(ConsoleOutputType.linear);
|
||||||
|
|
||||||
terminal.setTitle("Basic I/O");
|
terminal.setTitle("Basic I/O");
|
||||||
auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw | ConsoleInputFlags.mouse | ConsoleInputFlags.paste);
|
auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw | ConsoleInputFlags.mouse | ConsoleInputFlags.paste | ConsoleInputFlags.size);
|
||||||
|
|
||||||
terminal.color(Color.green | Bright, Color.black);
|
terminal.color(Color.green | Bright, Color.black);
|
||||||
|
|
||||||
|
@ -1611,6 +1710,13 @@ void main() {
|
||||||
void handleEvent(InputEvent event) {
|
void handleEvent(InputEvent event) {
|
||||||
terminal.writef("%s\n", event.type);
|
terminal.writef("%s\n", event.type);
|
||||||
final switch(event.type) {
|
final switch(event.type) {
|
||||||
|
case InputEvent.Type.UserInterruptionEvent:
|
||||||
|
timeToBreak = true;
|
||||||
|
break;
|
||||||
|
case InputEvent.Type.SizeChangedEvent:
|
||||||
|
auto ev = event.get!(InputEvent.Type.SizeChangedEvent);
|
||||||
|
terminal.writeln(ev);
|
||||||
|
break;
|
||||||
case InputEvent.Type.CharacterEvent:
|
case InputEvent.Type.CharacterEvent:
|
||||||
auto ev = event.get!(InputEvent.Type.CharacterEvent);
|
auto ev = event.get!(InputEvent.Type.CharacterEvent);
|
||||||
terminal.writef("\t%s\n", ev);
|
terminal.writef("\t%s\n", ev);
|
||||||
|
|
Loading…
Reference in New Issue