On Thursday, 14 May 2026 at 10:11:47 UTC, Darren Drapkin wrote:
This seems to work, if I remember to press enter.

Yes, the kernel requires you to press enter before it sends info to the program unless you switch the mode.

Switching the mode is not terribly hard on its own, but once you are in raw mode, if you want to read things like arrow keys, it gets pretty complicated. The terminals send all kinds of legacy garbage down the file you have to parse out.

If you want to use a library for this, well, I kinda just wrote a whole sample program using mine...

https://github.com/adamdruppe/arsd/

terminal.d and core.d from there all you need.

or it is `arsd-official:terminal` on dub if you use that.

anyway my sample program:

```
import arsd.terminal;
static import std.stdio; // do not want stdio writeln too easy to access

void main() {
        int terminalFd = 0;

        if(!Terminal.stdoutIsTerminal) {
                throw new Exception("no terminal to write to");
        }

        string fileContents;
        string[] originalFileLines;
        if(Terminal.stdinIsTerminal) {
                // stdin is still a terminal, so nothing as piped to us
                // the fileContents can be empty or read from arguments
                // or whatever. just leaving it blank for this demo.
        } else {
                // read whatever was sent to us in stdin
                foreach(chunk; std.stdio.stdin.byChunk(4096 * 8))
                        fileContents ~= cast(string) chunk;

// split the lines and replace some chars for easier drawing later
                import std.string;
originalFileLines = splitLines(fileContents.replace("\t", " "));

                // we can use stdout (file #1) as our handle to the terminal
                // instead of the normal one, since we know from above it
                // is a terminal or else we'd have thrown earlier
                terminalFd = 1;
        }

// create a terminal object using the possibly modified fd from above // cellular mode means using the alternate screen, so we can do full screen
        // writes to it
        auto terminal = Terminal(ConsoleOutputMode.cellular, terminalFd);

        // get real time input, raw with no echo
auto rtti = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw);

        int scrollPosition = 0;

        mainLoop: while(true) {
                // this redraw isn't very efficient but it is simple to code
                // just clear and redo everything every time.
                terminal.clear();

                string[] lines;
                lines.reserve(originalFileLines.length);

auto maxWidth = terminal.width - 1; // leave room for some margin;
                foreach(idx, line; originalFileLines) {
                        // wrap the long lines so they fit in the terminal.
                        // this wrap function sucks, it doesn't handle unicode 
chars
                        // or word breaks or anything
                        if(line.length > maxWidth) {
                                while(line.length > maxWidth) {
                                        lines ~= line[0 .. maxWidth];
                                        line = line[maxWidth .. $];
                                }
                        } else {
                                lines ~= line;
                        }

                }

                int lineCount = cast(int) lines.length;

foreach(y; 0 .. terminal.height - 1) { // leave room for info at the end
                        auto lineToDraw = y + scrollPosition;
                        if(lineToDraw < 0)
                                continue;
                        if(lineToDraw >= lineCount)
                                break;

                        terminal.writeln(lines[lineToDraw]);
                }

                // write that info at the end
                terminal.write(scrollPosition, "/", lineCount);

                ignore:
                auto c = rtti.getch();
                switch(c) {
                        case 'q':
                                break mainLoop;
                        case KeyboardEvent.Key.Home:
                                scrollPosition = 0;
                        break;
                        case KeyboardEvent.Key.End:
                                scrollPosition = lineCount - terminal.height;
                                if(scrollPosition < 0)
                                        scrollPosition = 0;
                        break;
                        case KeyboardEvent.Key.PageDown:
                                scrollPosition += terminal.height;
                        break;
                        case KeyboardEvent.Key.PageUp:
                                scrollPosition -= terminal.height;
                                if(scrollPosition < 0)
                                        scrollPosition = 0;
                        break;
                        case KeyboardEvent.Key.DownArrow:
                                scrollPosition++;
                        break;
                        case KeyboardEvent.Key.UpArrow:
                                scrollPosition--;
                        break;
                        default:
                                goto ignore;
                }
        }
}
```

Checking terminal.width and height is an ioctl and signal handler the lib does for you (the way this program is written, if you resize the terminal, you need to press an arrow key to display the update again, but you can handle all those in a loop too if you wanted to), processing those KeyboardEvent.Keys is a mess of ugly code it handles for you, etc.

Then the trick of passing the terminalFd lets it bypass the redirected stdin and use your output terminal instead. You could also open("/dev/tty") like i said in my first message if both in and out are redirected to still get to it, passing the fd you get from `open` to the Terminal object.

And like the comment says here this code isn't made to be efficient lol but meh good enough.

Anyway going from a sample to a real program can be a lot of work with or without the terminal library! Just terminal code is so hideous using a lib for it does make some real sense.

Reply via email to