How to get single keystroke in D2 (Phobos)?
Asked Answered
M

2

8

Is there a simple, cross-platform way to get a single keystroke in D2 using Phobos?

For instance, a "Press any key to continue..." prompt, or a Brainfuck interpreter.

All the methods I've tried require an Enter keypress before passing the input (getchar(), for instance).

Mavilia answered 21/3, 2011 at 0:27 Comment(4)
Please be more specific. D1 or D2? Phobos or Tango?Oahu
In C, the getch function from the non-standard conio.h header does what you want. From what I can tell, though, it's not in D's std.c.* however. I'll keep looking.Spiny
This thread has some relevant information: digitalmars.com/d/archives/digitalmars/D/learn/…Spiny
I just do Press ENTER to continue... and get it over with. :\Masao
S
5

I've done some research on the matter, and I've found that, while the Phobos library under D 1.0 had exactly what you need in the form of std.c.stdio.getch(), D 2.0 lacks this function. None of the other standard input functions in Phobos appear to have the behavior you want.

As I understand it, this is because the desired behavior (that is, getting a single character without the need for an Enter keypress) is rather nonstandard and has to be implemented in relatively ugly, platform-specific ways. (In its initial form, the function getch existed in C's <conio.h>, a DOS-specific header that has become somewhat of a de facto cross-platform standard despite not being part of the standard C library.) Evidently the maintainers of the Phobos runtime library decided to strip out this particular bit of backwards-compatible functionality in the name of a cleaner library, but at the expense of this functionality.

Manual declaration

Reportedly, you can get around this missing function declaration by adding this to your source file:

extern (C) int getch();

However, I have found that this produces a linker error, suggesting that the function was removed from the runtime library entirely, rather than merely having its declaration removed from std.c.stdio. It's certainly worth a try—it might happen to work on your system and compiler, I really don't know.

Edit 2: This actually seems to work on Windows; it failed for me on the Linux side. It appears that DMD under Windows first links to the Phobos/D runtime (phobos.lib), then the C runtime (snn.lib); however, on Linux, DMD links to one runtime library that supplies both parts. This difference appears to cause linkage to undeclared functions (getch among them) to only work on Windows. If Windows is the only platform you're concerned with, this solution is probably suitable. If you need more cross-platform compatibility, read on.

ncurses

Another possibility is to use the ncurses library. It implements a getch function that will definitely do what you want—provided that you're cool with finding D bindings for the library or just using the C interface. Be aware that it requires a smidgen more setup than just calling the function you want; this thread has more information on the matter.

D 1.0

Now, for some considerably uglier solutions. Using D 1.0 would allow you to find what you need in the Phobos standard library—but this obviously entails using the older, crustier version of the language and I personally do not find the lack of one console IO function in the standard library to be justification for using the old version of D.

I believe that Tango also lost its getch declaration (under tango.stdc.stdio) over the switch to D 2.0, but my knowledge of Tango is extremely limited, so I may be wrong.

Write it yourself

If you're determined, you could just write your own getch. I wasn't able to find a cross-platform C implementation of getch using Google Code Search, which leaves me pessimistic about the likelihood of a relatively simple, 10-line-or-so function implementation that could simply be adapted to D.

On the other hand, Walter Bright—you know, the guy who designed the D language—provides a D implementation of just this sort of function here. However, even this appears to be somewhat outdated, because one of the symbols, cfmakeraw, is undefined under the current version of the DMD2 compiler. However, it's really close to being a workable solution.

Spiny answered 21/3, 2011 at 21:31 Comment(1)
An addendum to the Manual declaration section: you might be able to add that function declaration to your code and then link to the Digital Mars C++ runtime library, which does define getch, as far as I'm aware. It probably won't work but it's worth mentioning. Another hacky solution for sure.Spiny
P
6

The simplest solution that works with D2 on Windows:

import std.stdio : writefln;

extern(C) int kbhit();
extern(C) int getch();

void main()
{
    while(!kbhit())
    {
        // keep polling
        // might use Thread.Sleep here to avoid taxing the cpu.
    }

    writefln("Key hit was %s.", cast(char)getch());
}

It might even work with D1, but I haven't tried it out.

Here's a Linux version, modified from Walter's post:

import std.stdio : writefln;
import std.c.stdio;
import std.c.linux.termios;

extern(C) void cfmakeraw(termios *termios_p);

void main() 
{
    termios  ostate;                 /* saved tty state */
    termios  nstate;                 /* values for editor mode */

       // Open stdin in raw mode
       /* Adjust output channel        */
    tcgetattr(1, &ostate);                       /* save old state */
    tcgetattr(1, &nstate);                       /* get base of new state */
    cfmakeraw(&nstate);
    tcsetattr(1, TCSADRAIN, &nstate);      /* set mode */

      // Read characters in raw mode
    writefln("The key hit is %s", cast(char)fgetc(stdin));

       // Close
    tcsetattr(1, TCSADRAIN, &ostate);       // return to original mode
}
Pictish answered 21/3, 2011 at 21:40 Comment(8)
Have you tried compiling this on your system? On my system (compiler is DMD2), I get linker errors for kbhit and getch. I believe they were removed from the runtime library (see my answer for details)—or it could just be my version of the compiler, I can't speak for the Windows version.Spiny
@Spiny I'm using DMD 2.052. kbhit and getch are defined in snn.lib, which if I'm not mistaken is a compiler-specific library(?). GDC compiles this example as well.Ostrom
I am on Linux x86_64 with DMD 2.049 (slightly behind the current 2.052). I'll look into whether anything like snn.lib exists on the Linux side. Perhaps this is a platform-specific library difference (wouldn't be surprising, given that kbhit and getch aren't declared in the standard library namespace).Spiny
snn.lib appears to be the Digital Mars C runtime library (see digitalmars.com/d/2.0/dmd-windows.html#linking). Unfortunately, the corresponding Linux page has nothing to say about which libraries are which.Spiny
Now I've found something telling: On the Windows side, the linker first links to phobos.lib (the D runtime), then links to snn.lib (the C runtime). But on Linux, everything appears to be contained in libphobos2.a (compare the linux/lib32 dir vs. the windows/lib dir in the DMD zip file). So it's apparent that things work fundamentally differently on different platforms, the result of which is that linking to C functions in the runtime library may not work all the time on Linux/Posix. :SSpiny
@jgottula: I've edited my post with a Linux solution to OP's problem, in case you haven't noticed. I've successfully compiled and used it on a recent Ubuntu installation.Ostrom
That's neat! What did you change to alleviate the linker error I was getting with Walter's solution?Spiny
I've added the cfmakeraw prototype. I just did a quick google search and found its prototype on a wiki site, and modified it a little for use with D.Ostrom
S
5

I've done some research on the matter, and I've found that, while the Phobos library under D 1.0 had exactly what you need in the form of std.c.stdio.getch(), D 2.0 lacks this function. None of the other standard input functions in Phobos appear to have the behavior you want.

As I understand it, this is because the desired behavior (that is, getting a single character without the need for an Enter keypress) is rather nonstandard and has to be implemented in relatively ugly, platform-specific ways. (In its initial form, the function getch existed in C's <conio.h>, a DOS-specific header that has become somewhat of a de facto cross-platform standard despite not being part of the standard C library.) Evidently the maintainers of the Phobos runtime library decided to strip out this particular bit of backwards-compatible functionality in the name of a cleaner library, but at the expense of this functionality.

Manual declaration

Reportedly, you can get around this missing function declaration by adding this to your source file:

extern (C) int getch();

However, I have found that this produces a linker error, suggesting that the function was removed from the runtime library entirely, rather than merely having its declaration removed from std.c.stdio. It's certainly worth a try—it might happen to work on your system and compiler, I really don't know.

Edit 2: This actually seems to work on Windows; it failed for me on the Linux side. It appears that DMD under Windows first links to the Phobos/D runtime (phobos.lib), then the C runtime (snn.lib); however, on Linux, DMD links to one runtime library that supplies both parts. This difference appears to cause linkage to undeclared functions (getch among them) to only work on Windows. If Windows is the only platform you're concerned with, this solution is probably suitable. If you need more cross-platform compatibility, read on.

ncurses

Another possibility is to use the ncurses library. It implements a getch function that will definitely do what you want—provided that you're cool with finding D bindings for the library or just using the C interface. Be aware that it requires a smidgen more setup than just calling the function you want; this thread has more information on the matter.

D 1.0

Now, for some considerably uglier solutions. Using D 1.0 would allow you to find what you need in the Phobos standard library—but this obviously entails using the older, crustier version of the language and I personally do not find the lack of one console IO function in the standard library to be justification for using the old version of D.

I believe that Tango also lost its getch declaration (under tango.stdc.stdio) over the switch to D 2.0, but my knowledge of Tango is extremely limited, so I may be wrong.

Write it yourself

If you're determined, you could just write your own getch. I wasn't able to find a cross-platform C implementation of getch using Google Code Search, which leaves me pessimistic about the likelihood of a relatively simple, 10-line-or-so function implementation that could simply be adapted to D.

On the other hand, Walter Bright—you know, the guy who designed the D language—provides a D implementation of just this sort of function here. However, even this appears to be somewhat outdated, because one of the symbols, cfmakeraw, is undefined under the current version of the DMD2 compiler. However, it's really close to being a workable solution.

Spiny answered 21/3, 2011 at 21:31 Comment(1)
An addendum to the Manual declaration section: you might be able to add that function declaration to your code and then link to the Digital Mars C++ runtime library, which does define getch, as far as I'm aware. It probably won't work but it's worth mentioning. Another hacky solution for sure.Spiny

© 2022 - 2024 — McMap. All rights reserved.