0

So I wrote the following simple program which asks the user for some input and afterwards just asks if they want to do that again or exit.

#include <conio.h> /* or use ncurses for getch() */
#include <errno.h>
#include <stdio.h>
#include <string.h>

#define TRUE 1
#define FALSE 0

void ReadConsoleInput(void) {
  char buffer[83];
  char *result;

  printf("\nInput line of text, followed by carriage return:\n");

  result = fgets(buffer, sizeof buffer, stdin);
  buffer[strcspn(result, "\r\n")] = '\0';

  if (!result) {
    printf(
        "An error occurred reading from the console:"
        " error code %d\n",
        errno);
  } else {
    printf("\nLine length = %d\nText = %s\n", (int)strlen(result), result);
  }
}

int Repeat() {
  printf("Again? (Y/N): \n");
  int ch;
  ch = getch();
  return (ch == 'Y' || ch == 'y') ? TRUE : FALSE;
}

int main() {
  do { /* infinite loop */
    ReadConsoleInput();
  } while (Repeat());
  return 0;
}

The program runs fine under Windows using mingw64 compile, but the behavior changes upon changing conio.h to ncurses.h and compiling it under linux. The program runs the ReadConsoleInput() function fine but after the user hits 'ENTER' the Repeat() function just displays and the program ends before the user can enter any char.

What is happening different under linux?

(Also as an aside, if the infinite loop is not the right technique for what I am trying to do please advise. I feel like most programs don't rely on an infinite loop test to stay open.)

Eventually I plan on expanding the program to storing the text using a tree data structure and learn from there but I would like to maintain cross platform unity as I go.

Richard
  • 56,349
  • 34
  • 180
  • 251
skyfire
  • 227
  • 1
  • 6
  • 3
    Why not get rid of `conio.h` altogether and replace it with a standards compliant `stdio.h`, change `getch` to `getchar` and just account for the `'\n'` that will remain in the *input buffer* after the user presses *Enter* after entering his choice? – David C. Rankin Jun 12 '17 at 02:25
  • 2
    RTFM: If you read the curses documentation for `getch()`, you will see its behaviour depends on various modes (cbreak, nocbreak, half-delay mode, noecho). In particular, it can timeout, in which case it returns an error code. You need to explicitly set those modes before calling `getch()`. – Peter Jun 12 '17 at 02:36
  • 1
    Or, better yet, stick to `curses.h` and link `ncurses` on Linux and `pdcurses` on windows. `curses` solves the "console portability" issue for you. –  Jun 12 '17 at 06:12
  • @peter yeah my bad, should have looked at the manual longer. – skyfire Jun 12 '17 at 10:18
  • @DavidC.Rankin the reason I avoided `getchar` was so the response only took 1 character before immediately running to exit or relooping the program. I would rather avoid the boiler plate code that goes w/ checking input length and etc. If this can be achieved using `getchar` please let me know! I am kinda new to C programming. – skyfire Jun 12 '17 at 10:24
  • @FelixPalmen I intended to do just that initially but for some reason `pdcurses` does not work under mintty on windows(cmd prompt it works fine tho). I prefer using a terminal emulator on windows like cmder. – skyfire Jun 12 '17 at 10:33

1 Answers1

2

Continuing from my comment, just get rid of conio.h altogether. It is 100% non-portable except for windoze/DOS. You can use getchar instead of getch and simply account for any remaining '\n' in the input buffer that remains after the user enters Y/N. For example you could make a portable implementation similar to the following:

#include <stdio.h>
#include <string.h>
#include <errno.h>

#define TRUE 1
#define FALSE 0

void ReadConsoleInput(void) {
    char buffer[83];
    char *result;

    printf("\nInput line of text, followed by carriage return:\n");

    result = fgets(buffer, sizeof buffer, stdin);

    if (!result) {
        printf(
            "An error occurred reading from the console:"
            " error code %d\n",
            errno);
    } else {
        buffer[strcspn(result, "\r\n")] = '\0';
        printf("\nLine length = %d\nText = %s\n", (int)strlen(result), result);
    }
}

int Repeat() {
    printf("Again? (Y/N): \n");
    int c, ch;
    ch = getchar();

    /* empty input buffer */
    for (c = getchar(); c != '\n' && c != EOF; c = getchar()) {}

    return (ch == 'Y' || ch == 'y') ? TRUE : FALSE;
}

int main (void) {
    do { /* infinite loop */
        ReadConsoleInput();
    } while (Repeat());
    return 0;
}

(you would want to check that ch is not EOF and handle accordingly to catch the user cancelling input, before you call getchar to empty the buffer)

Example Use/Output

$ ./bin/noconio

Input line of text, followed by carriage return:
a quick brown fox

Line length = 17
Text = a quick brown fox
Again? (Y/N):
y

Input line of text, followed by carriage return:
jumps over a lazy dog

Line length = 21
Text = jumps over a lazy dog
Again? (Y/N):
n

Look things over and let me know if this is what you intended.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • 1
    Given that the program is trying to read a character directly from keyboard, any solution is non-portable. Standard I/O can't be used. – Peter Jun 12 '17 at 02:35
  • No, you can always put the keyboard in NOECHO mode via `struct termios` and do the exact same thing. – David C. Rankin Jun 12 '17 at 02:37
  • 1
    This is the second time in a week I have seen the `for` loop version of clearing the input stream.... Must be what the cool kids are doing now ;) AFAIK, `ncurses` is portable, even to Windows, and may better suit OP's purpose. – ad absurdum Jun 12 '17 at 02:37
  • 1
    Ahh, it just save me a line `:)` and I"m getting lazy... Agree 100% on the `ncurses` portability, but given the state of the code, it did not look like there was any attempt at a ncurses implementation. (personally, I'd change the keyboard mode to minic `getch` if that is wanted before adding a new library requirement) – David C. Rankin Jun 12 '17 at 02:39
  • `buffer[strcspn(result, "\r\n")] = '\0'; if (!result) {` is UB when `result == NULL`. – chux - Reinstate Monica Jun 12 '17 at 03:43
  • @DavidC.Rankin could you explain the `struct terminos` comment? My eventual goal is to link the command program to a user interface(Albeit I am a bit aways from figuring that part out). Also won't using `terminos.h` force a linux only solution? – skyfire Jun 12 '17 at 10:29
  • Sure, if you want your keyboard to interpret keys immediately, without requiring the user press 'Enter' (like `getch`), you can put the keyboard in noncanonical mode using `struct termios` and `tcsetattr` see: [**How to avoid press enter with any getchar()**](https://stackoverflow.com/questions/1798511/how-to-avoid-press-enter-with-any-getchar) The 2nd Answer. – David C. Rankin Jun 12 '17 at 19:40