conio.h Library Replacement

deceptikon 2 Tallied Votes 1K Views Share

I've needed to write sample code using conio.h over the years, but my compilers don't support all of it (most notable being clrscr() and gotoxy()). So I wrote a conio simulator class to help me. Not all of the conio.h functions are present, such as cgets() and cscanf(), because I haven't needed them. But the design is such that they can easily be added.

It's based around an abstract IConio class that can be inherited from for specific implementations. The conio.h library is very platform specific, and I've included a Win32 implementation as I work primarily with Windows systems. POSIX implementations aren't especially difficult, but I don't have the confidence in writing something I'd want to show off. ;)

Here's an example program based on one of the samples I wrote recently:

#include <iostream>
#include <string>
#include <Windows.h>
#include "coniolib_win32.h"

namespace {
    const console::IConio& conio = console::Win32Conio();

    enum {
        KEY_ENTER = 13,
        KEY_ESC   = 27,
        KEY_UP    = 256 + 72,
        KEY_DOWN  = 256 + 80,
        KEY_LEFT  = 256 + 75,
        KEY_RIGHT = 256 + 77
    };

    enum {
        HILITE_SELECTED   = 433,
        HILITE_UNSELECTED = 23
    };

    int get_key(void)
    {
        int ch = conio.getch();

        if (ch == 0 || ch == 224) {
            ch = 256 + conio.getch();
        }

        return ch;
    }
}

int menu(int selected_row = 1)
{
    const int size = 3;
    const std::string rows[size] = {
        "1) Option 1",
        "2) Option 2",
        "3) Option 3"
    };

    conio.clrscr();

    if (selected_row < 1) {
        selected_row = 1;
    }

    if (selected_row > size) {
        selected_row = size;
    }

    for (int i = 0; i < size; i++) {
        if (i + 1 == selected_row) {
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HILITE_SELECTED);
        }

        std::cout << rows[i] << '\n';

        if (i + 1 == selected_row) {
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HILITE_UNSELECTED);
        }
    }

    std::cout.flush();

    return selected_row;
}

void execute_selection(int selection)
{
    std::cout << "You selected option " << selection << '\n';
}

int main()
{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), HILITE_UNSELECTED);

    int selected_row = menu(1);
    bool done = false;

    while (!done) {
        Sleep(100);

        if (conio.kbhit()) {
            switch (get_key()) {
            case KEY_UP:
                selected_row = selected_row > 1 ? selected_row - 1 : selected_row;
                menu(selected_row);
                break;
            case KEY_DOWN:
                selected_row = selected_row < 3 ? selected_row + 1 : selected_row;
                menu(selected_row);
                break;
            case KEY_ENTER:
                execute_selection(selected_row);
                break;
            case KEY_ESC:
                done = true;
                break;
            default:
                break; // Ignore unsupported keys
            }
        }
    }
}
// coniolib.h
//     Defines the abstract interface for concrete implementations
#ifndef CONIOLIB_H
#define CONIOLIB_H

namespace console {
    class IConio {
    public:
        virtual void clrscr() const = 0;
        virtual void gotoxy(int x, int y) const = 0;
        virtual int wherex() const = 0;
        virtual int wherey() const = 0;
        virtual bool kbhit() const = 0;
        virtual int getch() const = 0;
        virtual int getche() const = 0;
        virtual int ungetch(int ch) const = 0;
        virtual int putch(int ch) const = 0;
    };
}

#endif

// coniolib_win32.h
//     Defines a concrete implementation class of IConio for Win32
#ifndef CONIOLIB_WIN32_H
#define CONIOLIB_WIN32_H

#include <Windows.h>
#include "coniolib.h"

namespace console {
    class Win32Conio: public IConio {
    public:
        Win32Conio();

        bool is_valid_in() const { return sys_stdin != INVALID_HANDLE_VALUE; }
        bool is_valid_out() const { return sys_stdout != INVALID_HANDLE_VALUE; }

        virtual void clrscr() const override;
        virtual void gotoxy(int x, int y) const override;
        virtual int wherex() const override;
        virtual int wherey() const override;
        virtual bool kbhit() const override;
        virtual int getch() const override;
        virtual int getche() const override;
        virtual int ungetch(int ch) const override;
        virtual int putch(int ch) const override;
    private:
        HANDLE sys_stdin;
        HANDLE sys_stdout;

        /* Support for ungetch() */
        mutable bool unget_avail;
        mutable int unget_char;
    };
}

#endif

// coniolib_win32.cpp
//     Method implementations for Win32Conio
#include <vector>
#include <Windows.h>
#include "coniolib_win32.h"

namespace console {
    Win32Conio::Win32Conio(): 
        sys_stdin(GetStdHandle(STD_INPUT_HANDLE)),
        sys_stdout(GetStdHandle(STD_OUTPUT_HANDLE)),
        unget_avail(false),
        unget_char(0)
    { }

    void Win32Conio::clrscr() const
    {
        if (!is_valid_out()) {
            // Not much can be done with an invalid console handle
            return;
        }

        CONSOLE_SCREEN_BUFFER_INFO info;

        if (GetConsoleScreenBufferInfo(sys_stdout, &info)) {
            DWORD size = info.dwSize.X * info.dwSize.Y;
            COORD topleft = {0};
            DWORD written;

            // Attempt to overwrite the visible buffer with blank spaces
            if (FillConsoleOutputCharacter(sys_stdout, ' ', size, topleft, &written)) {
                // Restore character attributes (color, etc...) for the blank space. Mostly
                // this just makes sure that non-default background color remains consistent.
                FillConsoleOutputAttribute(sys_stdout, info.wAttributes, size, topleft, &written);

                // Finally, reset the cursor position to place subsequent output at {0,0}
                SetConsoleCursorPosition(sys_stdout, topleft);
            }
        }
    }

    void Win32Conio::gotoxy(int x, int y) const
    {
        if (!is_valid_out()) {
            // Not much can be done with an invalid console handle
            return;
        }

        COORD pos;

        // Conio positions are 1-based, but Win32 positions 
        // are 0-based. Clamp to the range of SHORT.
        //
        // Note: std::min and std::max aren't used because
        //       windows.h defines macros of the same name.
        pos.X = (SHORT)min(max(1, x), 32767) - 1;
        pos.Y = (SHORT)min(max(1, y), 32767) - 1;

        SetConsoleCursorPosition(sys_stdout, pos);
    }

    int Win32Conio::wherex() const
    {
        if (!is_valid_out()) {
            // Not much can be done with an invalid console handle
            return -1;
        }

        CONSOLE_SCREEN_BUFFER_INFO info;

        if (!GetConsoleScreenBufferInfo(sys_stdout, &info)) {
            return -1;
        }

        // Conio positions are 1-based, but Win32 positions are 0-based
        return (int)info.dwCursorPosition.X + 1;
    }

    int Win32Conio::wherey() const
    {
        if (!is_valid_out()) {
            // Not much can be done with an invalid console handle
            return -1;
        }

        CONSOLE_SCREEN_BUFFER_INFO info;

        if (!GetConsoleScreenBufferInfo(sys_stdout, &info)) {
            return -1;
        }

        // Conio positions are 1-based, but Win32 positions are 0-based
        return (int)info.dwCursorPosition.Y + 1;
    }

    bool Win32Conio::kbhit() const
    {
        if (!is_valid_in()) {
            // Not much can be done with an invalid console handle
            return false;
        }

        bool key_ready = false;
        DWORD saved_mode;

        // Save the current mode and switch to raw mode
        if (GetConsoleMode(sys_stdin, &saved_mode) && SetConsoleMode(sys_stdin, 0)) {
            DWORD pending;

            // Get the number of pending input events (including key presses)
            if (GetNumberOfConsoleInputEvents(sys_stdin, &pending) && pending) {
                std::vector<INPUT_RECORD> info(pending);
                DWORD read;

                // Peek at and check each pending input event for a key press
                if (PeekConsoleInput(sys_stdin, info.data(), pending, &read) && read) {
                    for (DWORD i = 0; i < read; ++i) {
                        if (info[i].EventType == KEY_EVENT && info[i].Event.KeyEvent.bKeyDown) {
                            key_ready = true;
                            break;
                        }
                    }
                }
            }

            // Restore the original console mode
            SetConsoleMode(sys_stdin, saved_mode);
        }

        return key_ready;
    }

    int Win32Conio::getch() const
    {
        if (!is_valid_in()) {
            // Not much can be done with an invalid console handle
            return -1;
        }

        if (unget_avail) {
            // There was a preceding ungetch
            unget_avail = false;
            return unget_char;
        }

        DWORD saved_mode;
        int ch = -1;

        // Save the current mode and switch to raw mode
        if (GetConsoleMode(sys_stdin, &saved_mode) && SetConsoleMode(sys_stdin, 0)) {
            // Poll key events until we get one we like
            while (ch == -1) {
                INPUT_RECORD info;
                DWORD read;

                // Attempt to read a raw character from the console
                if (!ReadConsoleInput(sys_stdin, &info, 1, &read) || !read) {
                    ch = -1;
                    break;
                }

                auto key = info.Event.KeyEvent;

                if (info.EventType == KEY_EVENT && key.bKeyDown) {
                    // Save the current code (may be an escape for extended codes)
                    ch = key.uChar.AsciiChar;

                    if (ch == 0 || ch == 0xe0) {
                        // Push the extended code for the next call
                        unget_avail = true;
                        unget_char = key.wVirtualScanCode;
                    }
                }
            }

            // Restore the original console mode
            SetConsoleMode(sys_stdin, saved_mode);
        }

        return ch;
    }

    int Win32Conio::getche() const
    {
        // Use a three step method rather than putch(getch()) in case
        // one of sys_stdin or sys_stdout are invalid, but not both.
        int ch = getch();

        if (ch != -1) {
            // Only push valid characters from getch
            putch(ch);
        }

        return ch;
    }

    int Win32Conio::ungetch(int ch) const
    {
        if (!is_valid_in() || unget_avail) {
            // Invalid console handle or there's already a buffered character
            return -1;
        }

        unget_avail = true;
        unget_char = ch;

        return ch;
    }

    int Win32Conio::putch(int ch) const
    {
        if (!is_valid_out()) {
            // Not much can be done with an invalid console handle
            return -1;
        }

        DWORD written;

        if (WriteFile(sys_stdout, &ch, 1, &written, 0)) {
            return -1;
        }

        return ch;
    }
}