c++linuxmacosconio

C++ how to take input and output at the same time (LINUX/MAC) without ncurses


i'm trying to get input and output to work at the same time, but without output messing it up. I got it to work with windows but not linux/mac (for getting the line from the terminal so I can print it below the selected outputted text)

code:

#include <iostream>
#include <string>
#include <thread>
#include <atomic>
#include <chrono>

#if defined(_WIN32)
#include <windows.h>
#include <conio.h>
#elif defined(__unix__) || defined(__APPLE__)
//libraries needed
#endif

using namespace std;

void ReadCin(atomic<bool>& run)
{
    string buffer;

    while (run.load())
    {
        getline(cin, buffer);

        if (buffer == "Quit")
        {
            run.store(false);
        }
    }
}

string get_terminal_line()
{
    string line;

#if defined(_WIN32)
    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if (GetConsoleScreenBufferInfo(hStdOut, &csbi))
    {
        DWORD dwSize = csbi.dwSize.X;
        COORD cursorPos = csbi.dwCursorPosition;
        cursorPos.X = 0;
        SetConsoleCursorPosition(hStdOut, cursorPos);

        for (DWORD i = 0; i < dwSize; ++i)
        {
            char ch;
            DWORD charsRead;
            if (ReadConsoleOutputCharacterA(hStdOut, &ch, 1, cursorPos, &charsRead))
            {
                line += ch;
                cursorPos.X++;
            }
            else
            {
                break;
            }
        }

        SetConsoleCursorPosition(hStdOut, csbi.dwCursorPosition);
    }

#elif defined(__unix__) || defined(__APPLE__)
    //here
#endif

    size_t end = line.find_last_not_of(' ');
    if (end != string::npos)
    {
        line.erase(end + 1);
    }
    else
    {
        line.clear();
    }

    return line;
}

void PrintNewLine(atomic<bool>& run)
{
    while (run.load())
    {
        this_thread::sleep_for(chrono::seconds(2));

        string line = get_terminal_line();

        cout << "\33[2K\rNew line printed every 10 seconds" << endl;

        bool is_space = true;

        for (size_t length = 0; length != line.length(); length++)
        {
            if (isspace(line[length]) == false)
            {
                is_space = false;
                break;
            }
        }

        if (is_space == false)
        {
            cout << line;
        }
    }
}

int main()
{
    atomic<bool> run(true);
    thread cinThread(ReadCin, ref(run));
    thread printThread(PrintNewLine, ref(run));

    while (run.load())
    {
      //idk what to put here, so i'm just gonna leave it empty
    }

    run.store(false);
    cinThread.join();
    printThread.join();

    return 0;
}

I want this to work with mac and linux (hence the macro define above) which just gets the line from the terminal.


Solution

  • In Linux, you control terminal input/output with the tcsetattr function which allows you to control various things that happen in the kernel/drivers between your program and the terminal.

    If I understand what you are asking, what you want is to turn off the automatic echoing of keyboard input, so you can position it properly with output. You do this with ECHO flag:

    #include <termios.h>
    
      :
    
         struct termios term;
         tcgetattr(0, &term);    // get the setting on the terminal connected to stdin
         term.c_lflag &= ~ECHO;  // turn off the ECHO flag
         tcsetattr(0, TCSANOW, &term);
    

    Now keyboard input will be "silent" -- not echoed to the screen, so if you want the input to be seen, you'll need to output it yourself. If you want to be able to get (and thus echo and see) keystrokes before an Enter is hit, you'll also want to clear the ICANON bit for canonical input handling:

    term.c_lflag &= ~(ECHO|ICANON);  // turn off echo and canonical input
    

    now every keystroke will be readable as soon as it is hit, and the terminal driver will not process backspace (you'll get actual backspace characters).