Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

win api readConsole()

I am trying to use Win API ReadConsole(...) and I want to pass in a delimiter char to halt the input from the console. The below code works but it only stops reading the input on \r\n. I would like it to stop reading the console input on '.' for instance.

void read(char *cIn, char delim)
{
    HANDLE hFile;
    DWORD charsRead;
    DWORD charsToRead = MAX_PATH;
    CONSOLE_READCONSOLE_CONTROL cReadControl;

    cReadControl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);
    cReadControl.nInitialChars = 0;
    cReadControl.dwCtrlWakeupMask = delim;
    cReadControl.dwControlKeyState = NULL;

    DWORD lpMode;



//    char cIn[MAX_PATH];    //-- buffer to hold data from the console

    hFile = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
                    FILE_SHARE_WRITE | FILE_SHARE_READ, NULL,
                    OPEN_EXISTING, 0, NULL);

    GetConsoleMode(hFile,&lpMode);
//    lpMode &= ~ENABLE_LINE_INPUT;   //-- turns off this flag
//    SetConsoleMode(hFile, lpMode);  //-- set the mode with the new flag off

    bool read = ReadConsole(hFile, cIn, charsToRead * sizeof(TCHAR), &charsRead, &cReadControl);
    cIn[charsRead - 2] = '\0';
}

I know there are other easy ways to do this but I am just trying to understand some of the win api functions and how to use them.

Thank you.

like image 234
dmaelect Avatar asked Dec 05 '22 14:12

dmaelect


1 Answers

I saw this question and assumed it would be trivial, but spent the last 30 minutes trying to figure it out and finally have something.

That dwCtrlWakeupMask is pretty poorly documented in CONSOLE_READCONSOLE_CONTROL. MSDN says "A user-defined control character used to signal that the read is complete.", but why is it called mask? Why is it a ULONG instead of a TCHAR or something like that? I tried feeding it chars and wchars and nothing happened, so there must be more to the story.

I took to the web searching for that particular variable and found this link: https://groups.google.com/forum/#!topic/golang-codereviews/KSp37ITmcUg It is a random Go library coder asking for help and the answer is that tab is 1 << '\t'. I tried it, and it works!

So, for future web searchers, dwCtrlWakeupMask is a bitmask of ASCII control characters that will cause ReadConsole to return. You can | together as many 1 << ctrl_chars as you like... but it cannot be arbitrary characters, since it is a bitmask in a 32 bit value, only the chars 1-31 (inclusive) are possible (this group btw is called control characters, it includes things like tab, backspace, bell; things that do not represent printable characters per se).

Thus, this mask:

cReadControl.dwCtrlWakeupMask = (1 << '\t') | (1 << 0x08);

Will cause ReadConsole to return when tab (\t) OR when backspace (0x08) is pressed.

The characters represented by ctrl+ some_ascii_value are the number of that letter in the english alphabet, starting at a == 1. So, ctrl+d is 4, and ctrl+z is 26.

Therefore, this will return when the user hits ctrl+d or ctrl+z:

cReadControl.dwCtrlWakeupMask = (1 << 4) | (1 << 26);

Note that the Linux terminal driver also returns on read when the user hits ctrl+d so this might be a nice compatibility thing.

I believe the point of this argument is to allow easier tab-completion in processed input mode; otherwise, you'd have to turn processed input off and process keys one by one to do that. Now you don't have to.... though tbh, I still prefer taking my input with ReadConsoleInput for interactive programs since you get much better control over it all.

But while there are a lot of other ways to do what you want - and using . as a delimiter is impossible here, since it has a value >= 32, so you need to do it yourself... understanding what this does is interesting to me anyway, and there's scarce resources on the web so I'm writing this up just for future reference.

Note that this does not appear to work in wineconsole so be sure you are on a real Windows box to test it out.

Now, dwControlKeyState is actually set BY the function. Your value passed in is ignored (at least as far as I can tell), but you can inspect it for the given flags when the function returns. So, for example, after calling ReadConsole and hitting the key, it will be 32 if your numlock was on. It will be 48 is numlock was on and you pressed shift+tab (and had numlock on). So you test it after the function returns.

I typically like MSDN docs but IMO they completely dropped the ball on explaining this parameter!

like image 108
Adam D. Ruppe Avatar answered Dec 23 '22 01:12

Adam D. Ruppe