12 August, 2020

How do you save and restore console modes in Windows?

Problem

You’re writing a console application in Windows and would like to change the console behaviour and restore it later.

Solution

Wrap SetConsoleMode() in a handle object

This doesn’t need much explanation. It uses a handle class whose constructor preserves the current console mode, then sets the new mode, and whose destructor puts the mode back to its original value.

Constructor

The constructor preserves the current mode, then sets and resets the desired flags.

  1. Use GetStdHandle() to get a handle for the appropriate console device.

  2. Use GetConsoleMode() to read and preserve the current mode.

  3. Use SetConsoleMode() to change the mode.

Destructor

The destructor restores the mode that was previously preserved by the constructor.

  1. Use GetStdHandle() to get a handle for the desired console device.

  2. Use SetConsoleMode() to set the console mode back to its original value.

Code

ConsoleMode.h

#pragma once
#include <Windows.h>

class ConsoleMode
{
public:
    // Valid console devices.
    enum class Device : DWORD
    {
        input = STD_INPUT_HANDLE,
        output = STD_OUTPUT_HANDLE,
        error = STD_ERROR_HANDLE
    };

    explicit ConsoleMode(Device device, DWORD flagsToSet, DWORD flagsToClear);
    ~ConsoleMode();

    ConsoleMode(const ConsoleMode&) = delete;
    ConsoleMode& operator=(const ConsoleMode&) = delete;

    ConsoleMode(ConsoleMode&&) = delete;
    ConsoleMode& operator=(ConsoleMode&&) = delete;

private:
    Device device_;
    DWORD previousMode_;
};

ConsoleMode.cpp

#include "ConsoleMode.h"

#include <stdexcept>

ConsoleMode::ConsoleMode(Device device, DWORD flagsToSet, DWORD flagsToClear) : device_{device}
{
    // Get a handle to the console device.
    HANDLE handle = GetStdHandle(static_cast<DWORD>(device_));
    if (handle == nullptr || handle == INVALID_HANDLE_VALUE)
    {
        throw std::runtime_error("Can't get handle to console device");
    }

    // Get the current console mode and save it.
    DWORD currentMode = 0;
    if (!GetConsoleMode(handle, &currentMode))
    {
        throw std::runtime_error("Can't get current console device mode");
    }
    previousMode_ = currentMode;

    // Change the console mode.
    currentMode |= flagsToSet;
    currentMode &= ~flagsToClear;
    if (!SetConsoleMode(handle, currentMode))
    {
        // It failed, so do our best to put it back how it was before bailing.
        SetConsoleMode(handle, previousMode_);
        throw std::runtime_error("Can't set console mode");
    }
}

ConsoleMode::~ConsoleMode()
{
    // Restore the console mode to its previous value.
    HANDLE handle = GetStdHandle(static_cast<DWORD>(device_));
    if (handle != nullptr && handle != INVALID_HANDLE_VALUE)
    {
        SetConsoleMode(handle, previousMode_);
    }
}

Usage

#include "ConsoleMode.h"

#include <Windows.h>

void RunConsoleModeApp()
{
    // Change the console input mode. The console input mode will be restored when inputMode
    // goes out of scope.
    ConsoleMode inputMode(
        ConsoleMode::Device::input,
        ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_MOUSE_INPUT,
        ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE);

    // Change the console output mode. The console output mode will be restored when outputMode
    // goes out of scope.
    ConsoleMode outputMode(
        ConsoleMode::Device::output,
        ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN,
        0);
    
    // Put your console mode application here. Who knows, maybe it'll be the next NetHack.
}