/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ConsoleAsioStd.cpp Date: 2021-6-8 Author: Reece ***/ #include #include "ConsoleAsioStd.hpp" namespace Aurora::Console::ConsoleAsioStd { #if defined(AURORA_PLATFORM_WIN32) || defined(AURORA_PLATFORM_LINUX) || defined(AURORA_PLATFORM_APPLE) #if defined(AURORA_PLATFORM_WIN32) using StreamDescriptor_t = asio::windows::stream_handle; #else using StreamDescriptor_t = asio::posix::stream_descriptor; #endif static const AuMach kLineBufferMax = 2048; static AuUInt8 gLineBuffer[kLineBufferMax] = { 0 }; static AuString gLineString = ""; static StreamDescriptor_t *gInputStream = {}; static StreamDescriptor_t *gOutputStream = {}; static asio::io_context gIOC; void Init() { #if defined(AURORA_PLATFORM_WIN32) if (GetConsoleWindow() == NULL) { if (!gRuntimeConfig.console.forceConsoleWindow) { return; } if (gRuntimeConfig.console.disableAll) { return; } auto ok = AllocConsole(); SysAssert(ok, "Request of a Win32 console yielded nada, forceConsole wasn't respected"); } else { // no always means no under UNIX targets; but on windows, if you're compiling a console app, dont be surprised to find console output // instead of attempting to detach from conhost, link as a WindowedApp and do not setup any windows, if you're looking for windowless // ignore gRuntimeConfig.console.disableAll } DWORD dwMode; bool ok; auto hOutput = GetStdHandle(STD_OUTPUT_HANDLE); SysAssert(hOutput != INVALID_HANDLE_VALUE, "Couldn't open STDOUT"); ok = GetConsoleMode(hOutput, &dwMode); SysAssert(ok, "Couldn't get console mode"); dwMode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING; ok = SetConsoleMode(hOutput, dwMode); SysAssert(ok, "Couldn't set console mode"); auto fileHandle = CreateFileA("CONIN$", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); SysAssert(fileHandle != INVALID_HANDLE_VALUE, "Couldn't open CONIN"); gInputStream = _new StreamDescriptor_t(gIOC, fileHandle); fileHandle = CreateFileA("CONOUT$", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); SysAssert(fileHandle != INVALID_HANDLE_VALUE, "Couldn't open CONOUT"); gOutputStream = _new StreamDescriptor_t(gIOC, fileHandle); #else // no always means no under UNIX targets // we *know* stdin/out should be available under these standards if (gRuntimeConfig.console.disableAll) { return; } gInputStream = _new StreamDescriptor_t(gIOC, STDIN_FILENO); gOutputStream = _new StreamDescriptor_t(gIOC, STDOUT_FILENO); #endif SysAssert(gInputStream, "Couldn't allocate input stream handler"); SysAssert(gOutputStream, "Couldn't allocate output stream handler"); Aurora::Console::Hooks::AddHook([](const Aurora::Console::ConsoleMessage &string) -> void { #if defined(DEBUG) && defined(AURORA_PLATFORM_WIN32) auto debugLine = string.ToSimplified() + "\r\n"; OutputDebugStringA(debugLine.c_str()); #endif auto writeLine = string.ToConsole(); #if defined(AURORA_PLATFORM_WIN32) writeLine += '\r'; #endif writeLine += '\n'; asio::write(*gOutputStream, asio::buffer(writeLine)); }); } static void ProcessLines() { AuMach index = 0; while ((index = gLineString.find("\n")) != AuString::npos) { auto line = gLineString.substr(0, index); gLineString = gLineString.substr(index + 1); if (line[line.size() - 1] == '\r') { line.pop_back(); } if (line.size()) { Aurora::Console::Commands::DispatchCommand(line); } } } static void StdInHandler(const asio::error_code &code, std::size_t bytesCopied) { if (bytesCopied == 0) { return; } gLineString.append(reinterpret_cast(gLineBuffer), bytesCopied); ProcessLines(); } void Pump() { if (!gInputStream) return; gInputStream->async_read_some(asio::buffer(gLineBuffer, kLineBufferMax), StdInHandler); gIOC.poll_one(); } void Exit() { if (gInputStream) { delete gInputStream; } if (gOutputStream) { delete gOutputStream; } } #else void Pump() { } void Init() { } void Exit() { } #endif }