Getting real-time input from the terminal

Getting real-time input from the console or terminal can take a fair amount of platform-specific code, quite a bit more than writing out colored output.

Getting ready

Download terminal.d from my Github and put it in your project directory.

How to do it…

Let's execute the following steps to get real-time input from the terminal:

  1. Import terminal.
  2. Create a Terminal object.
  3. Create a RealTimeConsoleInput object, passing it a pointer to your Terminal object and passing flags for the types of input you need.
  4. Use the input object's methods.

The code is as follows:

import terminal;

void main() {
  auto terminal = Terminal(ConsoleOutputType.linear);
  auto input = RealTimeConsoleInput(&terminal, ConsoleInputFlags.raw);
  terminal.writeln("Press any key to exit");
  auto ch = input.getch();
  terminal.writeln("Bye!");
}

Now, run the program. It will immediately exit as soon as you press any key without waiting for a full line.

How it works…

By default, text input from the user is line buffered by the operating system. This means your program won't be able to process any of the data the user types until she or he either presses the Enter key or signals the end of the file (Ctrl + D on most Posix setups and Ctrl + Z on Windows). To get real-time input, this line buffering must be disabled.

RealTimeConsoleInput is a struct that disables this buffering in its constructor and resets the settings to normal in its destructor. Like Terminal, the default constructor and postblit functions of RealTimeConsoleInput are annotated with @disable to force correct use of the shared resource.

There's more…

The terminal.d module also has optional integration with another file in my Github repository, eventloop.d. To try it, compile both files together and add –version=with_eventloop to the compile command. The terminal.d module includes a commented-out demo function to show the usage. The eventloop.d module implements a generic event loop through which any kind of message can be passed from any number of sources, identified by type. The goal of the event loop is to allow arbitrary combinations of event-driven libraries, such as a terminal and a GUI window, in a single application.

The implementation of eventloop.d passes messages in two parts: a type identifier and a pointer to the data. The type identifier is created by hashing the type's mangled name, which is acquired with typeof(data).mangleof. A type's mangled name is guaranteed to be unique across an application because a non-unique name will cause a compile-time name clash error. Alternatively, a handle to typeid could have been used. This pair of numbers is sent through an operating system pipe back to the application itself, and it can be handled in a generic event loop as another file descriptor, watched by functions such as select or poll.

An interesting consequence of this implementation is that pointers to the garbage-collected data cannot be reliably sent as a message. The reason is that the garbage collector cannot see the kernel's pipe buffer. Consider the following: you send a message, then allocate some memory before receiving the message. The allocation triggers a garbage collection run.

As the garbage collector cannot see the pipe buffer, it may not find any reference to the message data and thus believe it is safe to free the data. Then, when the message is read, the pointer is invalid.

To fix this bug, I opted for manual memory management in the event loop's send and receive functions. When you send a message, it uses malloc to allocate a copy of the message. When the message is received, it frees the memory.

The eventloop.d module only contains a Linux epoll and self-pipe-based implementation at this time.

See also

  • The ncurses C library also provides real-time input functionality
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.204.5