-
-
Save rjmcguire/58f3fd3d5f0934dc351cd143c1b0c880 to your computer and use it in GitHub Desktop.
Little keyboard io channel experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!../bin/dub | |
/+ dub.sdl: | |
name "test_io_keyboard_input" | |
dependency "io" version="*" | |
dependency "libasync" version="*" | |
authors "Rory McGuire <rjmcguire@gmail.com>" | |
copyright "Copyright © 2016, rmcguire" | |
license "MIT" | |
+/ | |
import libasync; | |
import io; | |
import core.sys.posix.termios; | |
shared private termios* old_termios_state; /// don't access this! | |
termios getTermiosState() { /// Use this! | |
import core.atomic; | |
termios ret; | |
auto tmp = cast(termios*)atomicLoad(old_termios_state); // make sure we don't check a half written value | |
if (tmp is null) { | |
assert(0, "termios state not available before module is initialized"); | |
} | |
ret = *tmp; // this is okay, because module constructor is the only one that changes old_termios_state | |
return ret; | |
} | |
shared static this() { | |
import core.atomic; | |
termios* tmp = new termios; | |
tcgetattr(1, tmp); | |
atomicStore(old_termios_state, cast(shared)tmp); | |
} | |
//extern(C) void cfmakeraw(termios *termios_p); | |
void terminalModeSet(int cmd){ | |
termios t; | |
tcgetattr(1,&t); | |
switch (cmd) { | |
case 1: | |
t.c_lflag &= ~ICANON; | |
t.c_lflag &= ~ECHO; | |
//t.c_cc[VTIME] = 0; // these allow | |
//t.c_cc[VMIN] = 0; // you to tweak how long you wait, see `man tcsetattr` for symantics | |
break; | |
// case 2: // RAW MODE | |
////t.c_iflag &= IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON; | |
////t.c_oflag &= OPOST; | |
////t.c_lflag &= ECHO | ECHONL | ICANON | ISIG | IEXTEN; | |
////t.c_cflag &= CSIZE | PARENB; | |
////t.c_cflag |= CS8; | |
//cfmakeraw(&t); | |
//break; | |
default: | |
t = getTermiosState(); | |
break; | |
} | |
tcsetattr(1,TCSANOW,&t); | |
} | |
class KBNotifier { | |
private const(ubyte)[] chunk; | |
AsyncNotifier notifier; | |
this(EventLoop evl) { | |
notifier = new AsyncNotifier(evl); | |
} | |
void trigger(const(ubyte)[] chunk) { | |
this.chunk = chunk; | |
notifier.trigger(); | |
} | |
bool run(void delegate(const(ubyte)[]) dg) { | |
return notifier.run({ | |
dg(chunk); | |
}); | |
} | |
alias notifier this; | |
} | |
void stdinput_keyboard(EventLoop evl, bool exit_at_ctrlD, KBNotifier onkeypress, // this is a source | |
AsyncNotifier onEOF, | |
AsyncNotifier destroy) // this is an sink, will only exit on next console event | |
{ | |
doOffThread({ | |
bool exitAtNext; | |
if (destroy) { | |
destroy.run({ | |
exitAtNext = true; | |
}); | |
} | |
// set terminal to send each character, not just after enter. set terminal to not echo keypresses | |
terminalModeSet(1); | |
scope(exit) terminalModeSet(0); | |
size_t lines = 0; | |
import core.stdc.stdio; | |
import core.sys.posix.unistd; | |
ubyte[1024] buf; | |
while (true) { | |
//auto ch = getc(stdin); // one character at a time, even escape sequences | |
auto n = read(0, buf.ptr, 10); | |
if (n==0) onEOF.trigger(); | |
if (n<0) onEOF.trigger(); // could add onError | |
// TODO: convert escape sequence to something (perhaps use the same map X uses, nice for code re-use, or possibly even SDL key codes). | |
// the below sequences are for ctrl+up ctrl+down ctrl+right ctrl+left | |
// ^[[1;5A^[[1;5B^[[1;5C^[[1;5D | |
// TODO: keeping remaining characters that are in buffer for next iteration | |
// TODO: change "ch" to be a KeyCode of somesort (perhaps use the same map X uses, nice for code re-use, or possibly even SDL key codes, OR Javascript equivalents!!!) | |
auto ch = cast(int)buf[0]; | |
if (exitAtNext) break; | |
//printfln("%s", ch); | |
if (ch==4) { | |
if (exit_at_ctrlD) { | |
break; | |
} | |
} | |
if (onkeypress) onkeypress.trigger(buf[0..n]); // TODO: This seems to need some way to make sure it waits until the handler completes before allowing the next foreach iteration. | |
// Getting changing output when piping stdin. cat test_io_stdin | ./test_io_stdin | head | |
++lines; | |
} | |
if (onEOF) onEOF.trigger(); | |
}); | |
} | |
void main() { | |
auto g_eventLoop = getThreadEventLoop(); | |
auto keypress = new KBNotifier(g_eventLoop); | |
keypress.run((scope buf) { | |
println("kp: ", buf); | |
}); | |
auto eof = new AsyncNotifier(g_eventLoop); | |
eof.run({ | |
println("oef"); | |
g_closed = true; | |
}); | |
auto kill_key_listener = new AsyncNotifier(g_eventLoop); | |
//after(10.seconds, kill_key_listener.trigger()) | |
//stdinput_keyboard(g_eventLoop, false, keypress, eof, kill_key_listener); // don't exit at ctrlD, this is needed if stdin is e.g. a executable file's data | |
stdinput_keyboard(g_eventLoop, true, keypress, eof, kill_key_listener); // exit if ctrlD pressed | |
import std.datetime : seconds; | |
while(!g_closed) g_eventLoop.loop(-1.seconds); | |
} | |
EventLoop g_eventLoop = void; | |
bool g_closed; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment