/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /// \addtogroup Acored /// @{ /// \file #include "CliRunnable.h" #include "Config.h" #include "ObjectMgr.h" #include "World.h" #include #if AC_PLATFORM != AC_PLATFORM_WINDOWS #include "Chat.h" #include "ChatCommand.h" #include #include #include #endif static constexpr char CLI_PREFIX[] = "AC> "; static inline void PrintCliPrefix() { fmt::print(CLI_PREFIX); } #if AC_PLATFORM != AC_PLATFORM_WINDOWS namespace Acore::Impl::Readline { static std::vector vec; char* cli_unpack_vector(char const*, int state) { static std::size_t i=0; if (!state) i = 0; if (i < vec.size()) return strdup(vec[i++].c_str()); else return nullptr; } char** cli_completion(char const* text, int /*start*/, int /*end*/) { ::rl_attempted_completion_over = 1; vec = Acore::ChatCommands::GetAutoCompletionsFor(CliHandler(nullptr,nullptr), text); return ::rl_completion_matches(text, &cli_unpack_vector); } int cli_hook_func() { if (World::IsStopped()) ::rl_done = 1; return 0; } } #endif void utf8print(void* /*arg*/, std::string_view str) { #if AC_PLATFORM == AC_PLATFORM_WINDOWS fmt::print(str); #else { fmt::print(str); fflush(stdout); } #endif } void commandFinished(void*, bool /*success*/) { PrintCliPrefix(); fflush(stdout); } #ifdef linux // Non-blocking keypress detector, when return pressed, return 1, else always return 0 int kb_hit_return() { struct timeval tv; fd_set fds; tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); select(STDIN_FILENO+1, &fds, nullptr, nullptr, &tv); return FD_ISSET(STDIN_FILENO, &fds); } #endif /// %Thread start void CliThread() { #if AC_PLATFORM == AC_PLATFORM_WINDOWS // print this here the first time // later it will be printed after command queue updates PrintCliPrefix(); #else ::rl_attempted_completion_function = &Acore::Impl::Readline::cli_completion; { static char BLANK = '\0'; ::rl_completer_word_break_characters = &BLANK; } ::rl_event_hook = &Acore::Impl::Readline::cli_hook_func; #endif if (sConfigMgr->GetOption("BeepAtStart", true)) printf("\a"); // \a = Alert #if AC_PLATFORM == AC_PLATFORM_WINDOWS if (sConfigMgr->GetOption("FlashAtStart", true)) { FLASHWINFO fInfo; fInfo.cbSize = sizeof(FLASHWINFO); fInfo.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG; fInfo.hwnd = GetConsoleWindow(); fInfo.uCount = 0; fInfo.dwTimeout = 0; FlashWindowEx(&fInfo); } #endif ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it while (!World::IsStopped()) { fflush(stdout); std::string command; #if AC_PLATFORM == AC_PLATFORM_WINDOWS wchar_t commandbuf[256]; if (fgetws(commandbuf, sizeof(commandbuf), stdin)) { if (!WStrToUtf8(commandbuf, wcslen(commandbuf), command)) { PrintCliPrefix(); continue; } } #else char* command_str = readline(CLI_PREFIX); ::rl_bind_key('\t', ::rl_complete); if (command_str != nullptr) { command = command_str; free(command_str); } #endif if (!command.empty()) { std::size_t nextLineIndex = command.find_first_of("\r\n"); if (nextLineIndex != std::string::npos) { if (nextLineIndex == 0) { #if AC_PLATFORM == AC_PLATFORM_WINDOWS PrintCliPrefix(); #endif continue; } command.erase(nextLineIndex); } fflush(stdout); sWorld->QueueCliCommand(new CliCommandHolder(nullptr, command.c_str(), &utf8print, &commandFinished)); #if AC_PLATFORM != AC_PLATFORM_WINDOWS add_history(command.c_str()); #endif } else if (feof(stdin)) { World::StopNow(SHUTDOWN_EXIT_CODE); } } }