Skip to content

Instantly share code, notes, and snippets.

@rjmcguire
Created September 12, 2016 08:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rjmcguire/58f3fd3d5f0934dc351cd143c1b0c880 to your computer and use it in GitHub Desktop.
Save rjmcguire/58f3fd3d5f0934dc351cd143c1b0c880 to your computer and use it in GitHub Desktop.
Little keyboard io channel experiment
#!../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