LCOV - code coverage report
Current view: top level - source/ps - CConsole.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 4 369 1.1 %
Date: 2023-01-19 00:18:29 Functions: 3 28 10.7 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "CConsole.h"
      21             : 
      22             : #include "graphics/Canvas2D.h"
      23             : #include "graphics/FontMetrics.h"
      24             : #include "graphics/TextRenderer.h"
      25             : #include "gui/CGUI.h"
      26             : #include "gui/GUIManager.h"
      27             : #include "lib/code_generation.h"
      28             : #include "lib/timer.h"
      29             : #include "lib/utf8.h"
      30             : #include "maths/MathUtil.h"
      31             : #include "ps/CLogger.h"
      32             : #include "ps/ConfigDB.h"
      33             : #include "ps/CStrInternStatic.h"
      34             : #include "ps/Filesystem.h"
      35             : #include "ps/GameSetup/Config.h"
      36             : #include "ps/Globals.h"
      37             : #include "ps/Hotkey.h"
      38             : #include "ps/Profile.h"
      39             : #include "ps/Pyrogenesis.h"
      40             : #include "ps/VideoMode.h"
      41             : #include "scriptinterface/ScriptInterface.h"
      42             : #include "scriptinterface/JSON.h"
      43             : 
      44             : #include <string_view>
      45             : #include <vector>
      46             : #include <wctype.h>
      47             : 
      48             : namespace
      49             : {
      50             : 
      51             : // For text being typed into the console.
      52             : constexpr int CONSOLE_BUFFER_SIZE = 1024;
      53             : 
      54             : const char* CONSOLE_FONT = "mono-10";
      55             : 
      56             : } // anonymous namespace
      57             : 
      58             : CConsole* g_Console = 0;
      59             : 
      60           0 : CConsole::CConsole()
      61             : {
      62           0 :     m_Toggle = false;
      63           0 :     m_Visible = false;
      64             : 
      65           0 :     m_VisibleFrac = 0.0f;
      66             : 
      67           0 :     m_Buffer = std::make_unique<wchar_t[]>(CONSOLE_BUFFER_SIZE);
      68           0 :     FlushBuffer();
      69             : 
      70           0 :     m_MsgHistPos = 1;
      71           0 :     m_CharsPerPage = 0;
      72             : 
      73           0 :     m_PrevTime = 0.0;
      74           0 :     m_CursorVisState = true;
      75           0 :     m_CursorBlinkRate = 0.5;
      76             : 
      77           0 :     m_QuitHotkeyWasShown = false;
      78             : 
      79           0 :     InsertMessage("[ 0 A.D. Console v0.15 ]");
      80           0 :     InsertMessage("");
      81           0 : }
      82             : 
      83             : CConsole::~CConsole() = default;
      84             : 
      85           0 : void CConsole::Init()
      86             : {
      87             :     // Initialise console history file
      88           0 :     m_MaxHistoryLines = 200;
      89           0 :     CFG_GET_VAL("console.history.size", m_MaxHistoryLines);
      90             : 
      91           0 :     m_HistoryFile = L"config/console.txt";
      92           0 :     LoadHistory();
      93             : 
      94           0 :     UpdateScreenSize(g_xres, g_yres);
      95             : 
      96             :     // Calculate and store the line spacing
      97           0 :     const CFontMetrics font{CStrIntern(CONSOLE_FONT)};
      98           0 :     m_FontHeight = font.GetLineSpacing();
      99           0 :     m_FontWidth = font.GetCharacterWidth(L'C');
     100           0 :     m_CharsPerPage = static_cast<size_t>(g_xres / m_FontWidth);
     101             :     // Offset by an arbitrary amount, to make it fit more nicely
     102           0 :     m_FontOffset = 7;
     103             : 
     104           0 :     m_CursorBlinkRate = 0.5;
     105           0 :     CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate);
     106           0 : }
     107             : 
     108           0 : void CConsole::UpdateScreenSize(int w, int h)
     109             : {
     110           0 :     m_X = 0;
     111           0 :     m_Y = 0;
     112           0 :     float height = h * 0.6f;
     113           0 :     m_Width = w / g_VideoMode.GetScale();
     114           0 :     m_Height = height / g_VideoMode.GetScale();
     115           0 : }
     116             : 
     117           0 : void CConsole::ShowQuitHotkeys()
     118             : {
     119           0 :     if (m_QuitHotkeyWasShown)
     120           0 :         return;
     121             : 
     122           0 :     std::string str;
     123           0 :     for (const std::pair<const SDL_Scancode_, KeyMapping>& key : g_HotkeyMap)
     124           0 :         if (key.second.front().name == "console.toggle")
     125           0 :             str += (str.empty() ? "Press " : " / ") + FindScancodeName(static_cast<SDL_Scancode>(key.first));
     126             : 
     127           0 :     if (!str.empty())
     128           0 :         InsertMessage(str + " to quit.");
     129             : 
     130           0 :     m_QuitHotkeyWasShown = true;
     131             : }
     132             : 
     133           0 : void CConsole::ToggleVisible()
     134             : {
     135           0 :     m_Toggle = true;
     136           0 :     m_Visible = !m_Visible;
     137             : 
     138             :     // TODO: this should be based on input focus, not visibility
     139           0 :     if (m_Visible)
     140             :     {
     141           0 :         ShowQuitHotkeys();
     142           0 :         SDL_StartTextInput();
     143           0 :         return;
     144             :     }
     145           0 :     SDL_StopTextInput();
     146             : }
     147             : 
     148           0 : void CConsole::SetVisible(bool visible)
     149             : {
     150           0 :     if (visible != m_Visible)
     151           0 :         m_Toggle = true;
     152           0 :     m_Visible = visible;
     153           0 :     if (visible)
     154             :     {
     155           0 :         m_PrevTime = 0.0;
     156           0 :         m_CursorVisState = false;
     157             :     }
     158           0 : }
     159             : 
     160           0 : void CConsole::FlushBuffer()
     161             : {
     162             :     // Clear the buffer and set the cursor and length to 0
     163           0 :     memset(m_Buffer.get(), '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
     164           0 :     m_BufferPos = m_BufferLength = 0;
     165           0 : }
     166             : 
     167           0 : void CConsole::Update(const float deltaRealTime)
     168             : {
     169           0 :     if (m_Toggle)
     170             :     {
     171           0 :         const float AnimateTime = .30f;
     172           0 :         const float Delta = deltaRealTime / AnimateTime;
     173           0 :         if (m_Visible)
     174             :         {
     175           0 :             m_VisibleFrac += Delta;
     176           0 :             if (m_VisibleFrac > 1.0f)
     177             :             {
     178           0 :                 m_VisibleFrac = 1.0f;
     179           0 :                 m_Toggle = false;
     180             :             }
     181             :         }
     182             :         else
     183             :         {
     184           0 :             m_VisibleFrac -= Delta;
     185           0 :             if (m_VisibleFrac < 0.0f)
     186             :             {
     187           0 :                 m_VisibleFrac = 0.0f;
     188           0 :                 m_Toggle = false;
     189             :             }
     190             :         }
     191             :     }
     192           0 : }
     193             : 
     194           0 : void CConsole::Render(CCanvas2D& canvas)
     195             : {
     196           0 :     if (!(m_Visible || m_Toggle))
     197           0 :         return;
     198             : 
     199           0 :     PROFILE3_GPU("console");
     200             : 
     201           0 :     DrawWindow(canvas);
     202             : 
     203           0 :     CTextRenderer textRenderer;
     204           0 :     textRenderer.SetCurrentFont(CStrIntern(CONSOLE_FONT));
     205             :     // Animation: slide in from top of screen.
     206           0 :     const float deltaY = (1.0f - m_VisibleFrac) * m_Height;
     207           0 :     textRenderer.Translate(m_X, m_Y - deltaY);
     208             : 
     209           0 :     DrawHistory(textRenderer);
     210           0 :     DrawBuffer(textRenderer);
     211             : 
     212           0 :     canvas.DrawText(textRenderer);
     213             : }
     214             : 
     215           0 : void CConsole::DrawWindow(CCanvas2D& canvas)
     216             : {
     217             :     std::vector<CVector2D> points =
     218             :     {
     219             :         CVector2D{m_Width, 0.0f},
     220             :         CVector2D{1.0f, 0.0f},
     221           0 :         CVector2D{1.0f, m_Height - 1.0f},
     222           0 :         CVector2D{m_Width, m_Height - 1.0f},
     223             :         CVector2D{m_Width, 0.0f}
     224           0 :     };
     225           0 :     for (CVector2D& point : points)
     226           0 :         point += CVector2D{m_X, m_Y - (1.0f - m_VisibleFrac) * m_Height};
     227             : 
     228           0 :     canvas.DrawRect(CRect(points[1], points[3]), CColor(0.0f, 0.0f, 0.5f, 0.6f));
     229           0 :     canvas.DrawLine(points, 1.0f, CColor(0.5f, 0.5f, 0.0f, 0.6f));
     230             : 
     231           0 :     if (m_Height > m_FontHeight + 4)
     232             :     {
     233           0 :         points = {
     234           0 :             CVector2D{0.0f, m_Height - static_cast<float>(m_FontHeight) - 4.0f},
     235           0 :             CVector2D{m_Width, m_Height - static_cast<float>(m_FontHeight) - 4.0f}
     236             :         };
     237           0 :         for (CVector2D& point : points)
     238           0 :             point += CVector2D{m_X, m_Y - (1.0f - m_VisibleFrac) * m_Height};
     239           0 :         canvas.DrawLine(points, 1.0f, CColor(0.5f, 0.5f, 0.0f, 0.6f));
     240             :     }
     241           0 : }
     242             : 
     243           0 : void CConsole::DrawHistory(CTextRenderer& textRenderer)
     244             : {
     245           0 :     int i = 1;
     246             : 
     247           0 :     std::deque<std::wstring>::iterator it; //History iterator
     248             : 
     249           0 :     std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
     250             : 
     251           0 :     textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     252             : 
     253           0 :     for (it = m_MsgHistory.begin();
     254           0 :             it != m_MsgHistory.end()
     255           0 :                 && (((i - m_MsgHistPos + 1) * m_FontHeight) < m_Height);
     256             :             ++it)
     257             :     {
     258           0 :         if (i >= m_MsgHistPos)
     259             :         {
     260           0 :             textRenderer.Put(
     261             :                 9.0f,
     262           0 :                 m_Height - static_cast<float>(m_FontOffset) - static_cast<float>(m_FontHeight) * (i - m_MsgHistPos + 1),
     263             :                 it->c_str());
     264             :         }
     265             : 
     266           0 :         i++;
     267             :     }
     268           0 : }
     269             : 
     270             : // Renders the buffer to the screen.
     271           0 : void CConsole::DrawBuffer(CTextRenderer& textRenderer)
     272             : {
     273           0 :     if (m_Height < m_FontHeight)
     274           0 :         return;
     275             : 
     276           0 :     const CVector2D savedTranslate = textRenderer.GetTranslate();
     277             : 
     278           0 :     textRenderer.Translate(2.0f, m_Height - static_cast<float>(m_FontOffset) + 1.0f);
     279             : 
     280           0 :     textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f));
     281           0 :     textRenderer.PutAdvance(L"]");
     282             : 
     283           0 :     textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     284             : 
     285           0 :     if (m_BufferPos == 0)
     286           0 :         DrawCursor(textRenderer);
     287             : 
     288           0 :     for (int i = 0; i < m_BufferLength; ++i)
     289             :     {
     290           0 :         textRenderer.PrintfAdvance(L"%lc", m_Buffer[i]);
     291           0 :         if (m_BufferPos - 1 == i)
     292           0 :             DrawCursor(textRenderer);
     293             :     }
     294             : 
     295           0 :     textRenderer.ResetTranslate(savedTranslate);
     296             : }
     297             : 
     298           0 : void CConsole::DrawCursor(CTextRenderer& textRenderer)
     299             : {
     300           0 :     if (m_CursorBlinkRate > 0.0)
     301             :     {
     302             :         // check if the cursor visibility state needs to be changed
     303           0 :         double currTime = timer_Time();
     304           0 :         if ((currTime - m_PrevTime) >= m_CursorBlinkRate)
     305             :         {
     306           0 :             m_CursorVisState = !m_CursorVisState;
     307           0 :             m_PrevTime = currTime;
     308             :         }
     309             :     }
     310             :     else
     311             :     {
     312             :         // Should always be visible
     313           0 :         m_CursorVisState = true;
     314             :     }
     315             : 
     316           0 :     if(m_CursorVisState)
     317             :     {
     318             :         // Slightly translucent yellow
     319           0 :         textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 0.8f));
     320             : 
     321             :         // Cursor character is chosen to be an underscore
     322           0 :         textRenderer.Put(0.0f, 0.0f, L"_");
     323             : 
     324             :         // Revert to the standard text color
     325           0 :         textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
     326             :     }
     327           0 : }
     328             : 
     329           0 : bool CConsole::IsEOB() const
     330             : {
     331           0 :     return m_BufferPos == m_BufferLength;
     332             : }
     333             : 
     334           0 : bool CConsole::IsBOB() const
     335             : {
     336           0 :     return m_BufferPos == 0;
     337             : }
     338             : 
     339           0 : bool CConsole::IsFull() const
     340             : {
     341           0 :     return m_BufferLength == CONSOLE_BUFFER_SIZE;
     342             : }
     343             : 
     344           0 : bool CConsole::IsEmpty() const
     345             : {
     346           0 :     return m_BufferLength == 0;
     347             : }
     348             : 
     349             : //Inserts a character into the buffer.
     350           0 : void CConsole::InsertChar(const int szChar, const wchar_t cooked)
     351             : {
     352             :     static int historyPos = -1;
     353             : 
     354           0 :     if (!m_Visible) return;
     355             : 
     356           0 :     switch (szChar)
     357             :     {
     358           0 :     case SDLK_RETURN:
     359           0 :         historyPos = -1;
     360           0 :         m_MsgHistPos = 1;
     361           0 :         ProcessBuffer(m_Buffer.get());
     362           0 :         FlushBuffer();
     363           0 :         return;
     364             : 
     365           0 :     case SDLK_TAB:
     366             :         // Auto Complete
     367           0 :         return;
     368             : 
     369           0 :     case SDLK_BACKSPACE:
     370           0 :         if (IsEmpty() || IsBOB()) return;
     371             : 
     372           0 :         if (m_BufferPos == m_BufferLength)
     373           0 :             m_Buffer[m_BufferPos - 1] = '\0';
     374             :         else
     375             :         {
     376           0 :             for (int j = m_BufferPos-1; j < m_BufferLength - 1; ++j)
     377           0 :                 m_Buffer[j] = m_Buffer[j + 1]; // move chars to left
     378           0 :             m_Buffer[m_BufferLength-1] = '\0';
     379             :         }
     380             : 
     381           0 :         m_BufferPos--;
     382           0 :         m_BufferLength--;
     383           0 :         return;
     384             : 
     385           0 :     case SDLK_DELETE:
     386           0 :         if (IsEmpty() || IsEOB())
     387           0 :             return;
     388             : 
     389           0 :         if (m_BufferPos == m_BufferLength - 1)
     390             :         {
     391           0 :             m_Buffer[m_BufferPos] = '\0';
     392           0 :             m_BufferLength--;
     393             :         }
     394             :         else
     395             :         {
     396           0 :             if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL])
     397             :             {
     398             :                 // Make Ctrl-Delete delete up to end of line
     399           0 :                 m_Buffer[m_BufferPos] = '\0';
     400           0 :                 m_BufferLength = m_BufferPos;
     401             :             }
     402             :             else
     403             :             {
     404             :                 // Delete just one char and move the others left
     405           0 :                 for(int j = m_BufferPos; j < m_BufferLength - 1; ++j)
     406           0 :                     m_Buffer[j] = m_Buffer[j + 1];
     407           0 :                 m_Buffer[m_BufferLength - 1] = '\0';
     408           0 :                 m_BufferLength--;
     409             :             }
     410             :         }
     411             : 
     412           0 :         return;
     413             : 
     414           0 :     case SDLK_HOME:
     415           0 :         if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL])
     416             :         {
     417           0 :             std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
     418             : 
     419           0 :             const int linesShown = static_cast<int>(m_Height / m_FontHeight) - 4;
     420           0 :             m_MsgHistPos = Clamp(static_cast<int>(m_MsgHistory.size()) - linesShown, 1, static_cast<int>(m_MsgHistory.size()));
     421             :         }
     422             :         else
     423             :         {
     424           0 :             m_BufferPos = 0;
     425             :         }
     426           0 :         return;
     427             : 
     428           0 :     case SDLK_END:
     429           0 :         if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL])
     430             :         {
     431           0 :             m_MsgHistPos = 1;
     432             :         }
     433             :         else
     434             :         {
     435           0 :             m_BufferPos = m_BufferLength;
     436             :         }
     437           0 :         return;
     438             : 
     439           0 :     case SDLK_LEFT:
     440           0 :         if (m_BufferPos)
     441           0 :             m_BufferPos--;
     442           0 :         return;
     443             : 
     444           0 :     case SDLK_RIGHT:
     445           0 :         if (m_BufferPos != m_BufferLength)
     446           0 :             m_BufferPos++;
     447           0 :         return;
     448             : 
     449             :     // BEGIN: Buffer History Lookup
     450           0 :     case SDLK_UP:
     451           0 :         if (m_BufHistory.size() && historyPos != static_cast<int>(m_BufHistory.size()) - 1)
     452             :         {
     453           0 :             historyPos++;
     454           0 :             SetBuffer(m_BufHistory.at(historyPos).c_str());
     455           0 :             m_BufferPos = m_BufferLength;
     456             :         }
     457           0 :         return;
     458             : 
     459           0 :     case SDLK_DOWN:
     460           0 :         if (m_BufHistory.size())
     461             :         {
     462           0 :             if (historyPos > 0)
     463             :             {
     464           0 :                 historyPos--;
     465           0 :                 SetBuffer(m_BufHistory.at(historyPos).c_str());
     466           0 :                 m_BufferPos = m_BufferLength;
     467             :             }
     468           0 :             else if (historyPos == 0)
     469             :             {
     470           0 :                 historyPos--;
     471           0 :                 FlushBuffer();
     472             :             }
     473             :         }
     474           0 :         return;
     475             :     // END: Buffer History Lookup
     476             : 
     477             :     // BEGIN: Message History Lookup
     478           0 :     case SDLK_PAGEUP:
     479             :     {
     480           0 :         std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
     481             : 
     482           0 :         if (m_MsgHistPos != static_cast<int>(m_MsgHistory.size()))
     483           0 :             m_MsgHistPos++;
     484           0 :         return;
     485             :     }
     486             : 
     487           0 :     case SDLK_PAGEDOWN:
     488           0 :         if (m_MsgHistPos != 1)
     489           0 :             m_MsgHistPos--;
     490           0 :         return;
     491             :     // END: Message History Lookup
     492             : 
     493           0 :     default: //Insert a character
     494           0 :         if (IsFull() || cooked == 0)
     495           0 :             return;
     496             : 
     497           0 :         if (IsEOB()) //are we at the end of the buffer?
     498           0 :             m_Buffer[m_BufferPos] = cooked; //cat char onto end
     499             :         else
     500             :         { //we need to insert
     501             :             int i;
     502           0 :             for (i = m_BufferLength; i > m_BufferPos; --i)
     503           0 :                 m_Buffer[i] = m_Buffer[i - 1]; // move chars to right
     504           0 :             m_Buffer[i] = cooked;
     505             :         }
     506             : 
     507           0 :         m_BufferPos++;
     508           0 :         m_BufferLength++;
     509             : 
     510           0 :         return;
     511             :     }
     512             : }
     513             : 
     514             : 
     515           0 : void CConsole::InsertMessage(const std::string& message)
     516             : {
     517             :     // (TODO: this text-wrapping is rubbish since we now use variable-width fonts)
     518             : 
     519             :     //Insert newlines to wraparound text where needed
     520           0 :     std::wstring wrapAround = wstring_from_utf8(message.c_str());
     521           0 :     std::wstring newline(L"\n");
     522           0 :     size_t oldNewline=0;
     523             :     size_t distance;
     524             : 
     525             :     //make sure everything has been initialized
     526           0 :     if (m_CharsPerPage != 0)
     527             :     {
     528           0 :         while (oldNewline + m_CharsPerPage < wrapAround.length())
     529             :         {
     530           0 :             distance = wrapAround.find(newline, oldNewline) - oldNewline;
     531           0 :             if (distance > m_CharsPerPage)
     532             :             {
     533           0 :                 oldNewline += m_CharsPerPage;
     534           0 :                 wrapAround.insert(oldNewline++, newline);
     535             :             }
     536             :             else
     537           0 :                 oldNewline += distance+1;
     538             :         }
     539             :     }
     540             :     // Split into lines and add each one individually
     541           0 :     oldNewline = 0;
     542             : 
     543             :     {
     544           0 :         std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
     545             : 
     546           0 :         while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
     547             :         {
     548           0 :             distance -= oldNewline;
     549           0 :             m_MsgHistory.push_front(wrapAround.substr(oldNewline, distance));
     550           0 :             oldNewline += distance+1;
     551             :         }
     552           0 :         wrapAround.erase(0, oldNewline);
     553           0 :         m_MsgHistory.push_front(std::move(wrapAround));
     554             :     }
     555           0 : }
     556             : 
     557           0 : const wchar_t* CConsole::GetBuffer()
     558             : {
     559           0 :     m_Buffer[m_BufferLength] = 0;
     560           0 :     return m_Buffer.get();
     561             : }
     562             : 
     563           0 : void CConsole::SetBuffer(const wchar_t* szMessage)
     564             : {
     565           0 :     int oldBufferPos = m_BufferPos; // remember since FlushBuffer will set it to 0
     566             : 
     567           0 :     FlushBuffer();
     568             : 
     569           0 :     wcsncpy(m_Buffer.get(), szMessage, CONSOLE_BUFFER_SIZE);
     570           0 :     m_Buffer[CONSOLE_BUFFER_SIZE-1] = 0;
     571           0 :     m_BufferLength = static_cast<int>(wcslen(m_Buffer.get()));
     572           0 :     m_BufferPos = std::min(oldBufferPos, m_BufferLength);
     573           0 : }
     574             : 
     575           0 : void CConsole::ProcessBuffer(const wchar_t* szLine)
     576             : {
     577           0 :     if (!szLine || wcslen(szLine) <= 0)
     578           0 :         return;
     579             : 
     580           0 :     ENSURE(wcslen(szLine) < CONSOLE_BUFFER_SIZE);
     581             : 
     582           0 :     m_BufHistory.push_front(szLine);
     583           0 :     SaveHistory(); // Do this each line for the moment; if a script causes
     584             :                    // a crash it's a useful record.
     585             : 
     586             :     // Process it as JavaScript
     587           0 :     std::shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
     588           0 :     ScriptRequest rq(*pScriptInterface);
     589             : 
     590           0 :     JS::RootedValue rval(rq.cx);
     591           0 :     pScriptInterface->Eval(CStrW(szLine).ToUTF8().c_str(), &rval);
     592           0 :     if (!rval.isUndefined())
     593           0 :         InsertMessage(Script::ToString(rq, &rval));
     594             : }
     595             : 
     596           0 : void CConsole::LoadHistory()
     597             : {
     598             :     // note: we don't care if this file doesn't exist or can't be read;
     599             :     // just don't load anything in that case.
     600             : 
     601             :     // do this before LoadFile to avoid an error message if file not found.
     602           0 :     if (!VfsFileExists(m_HistoryFile))
     603           0 :         return;
     604             : 
     605           0 :     std::shared_ptr<u8> buf; size_t buflen;
     606           0 :     if (g_VFS->LoadFile(m_HistoryFile, buf, buflen) < 0)
     607           0 :         return;
     608             : 
     609           0 :     CStr bytes ((char*)buf.get(), buflen);
     610             : 
     611           0 :     CStrW str (bytes.FromUTF8());
     612           0 :     size_t pos = 0;
     613           0 :     while (pos != CStrW::npos)
     614             :     {
     615           0 :         pos = str.find('\n');
     616           0 :         if (pos != CStrW::npos)
     617             :         {
     618           0 :             if (pos > 0)
     619           0 :                 m_BufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos));
     620           0 :             str.erase(0, pos + 1);
     621             :         }
     622           0 :         else if (str.length() > 0)
     623           0 :             m_BufHistory.push_front(str);
     624             :     }
     625             : }
     626             : 
     627           0 : void CConsole::SaveHistory()
     628             : {
     629           0 :     WriteBuffer buffer;
     630           0 :     const int linesToSkip = static_cast<int>(m_BufHistory.size()) - m_MaxHistoryLines;
     631           0 :     std::deque<std::wstring>::reverse_iterator it = m_BufHistory.rbegin();
     632           0 :     if(linesToSkip > 0)
     633           0 :         std::advance(it, linesToSkip);
     634           0 :     for (; it != m_BufHistory.rend(); ++it)
     635             :     {
     636           0 :         CStr8 line = CStrW(*it).ToUTF8();
     637           0 :         buffer.Append(line.data(), line.length());
     638             :         static const char newline = '\n';
     639           0 :         buffer.Append(&newline, 1);
     640             :     }
     641             : 
     642           0 :     if (g_VFS->CreateFile(m_HistoryFile, buffer.Data(), buffer.Size()) == INFO::OK)
     643           0 :         ONCE(debug_printf("FILES| Console command history written to '%s'\n", m_HistoryFile.string8().c_str()));
     644             :     else
     645           0 :         debug_printf("FILES| Failed to write console command history to '%s'\n", m_HistoryFile.string8().c_str());
     646           0 : }
     647             : 
     648           0 : static bool isUnprintableChar(SDL_Keysym key)
     649             : {
     650           0 :     switch (key.sym)
     651             :     {
     652             :     // We want to allow some, which are handled specially
     653           0 :     case SDLK_RETURN: case SDLK_TAB:
     654             :     case SDLK_BACKSPACE: case SDLK_DELETE:
     655             :     case SDLK_HOME: case SDLK_END:
     656             :     case SDLK_LEFT: case SDLK_RIGHT:
     657             :     case SDLK_UP: case SDLK_DOWN:
     658             :     case SDLK_PAGEUP: case SDLK_PAGEDOWN:
     659           0 :         return true;
     660             : 
     661             :     // Ignore the others
     662           0 :     default:
     663           0 :         return false;
     664             :     }
     665             : }
     666             : 
     667           7 : InReaction conInputHandler(const SDL_Event_* ev)
     668             : {
     669           7 :     if (!g_Console)
     670           7 :         return IN_PASS;
     671             : 
     672           0 :     if (static_cast<int>(ev->ev.type) == SDL_HOTKEYPRESS)
     673             :     {
     674           0 :         std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
     675             : 
     676           0 :         if (hotkey == "console.toggle")
     677             :         {
     678           0 :             ResetActiveHotkeys();
     679           0 :             g_Console->ToggleVisible();
     680           0 :             return IN_HANDLED;
     681             :         }
     682           0 :         else if (g_Console->IsActive() && hotkey == "copy")
     683             :         {
     684           0 :             std::string text = utf8_from_wstring(g_Console->GetBuffer());
     685           0 :             SDL_SetClipboardText(text.c_str());
     686           0 :             return IN_HANDLED;
     687             :         }
     688           0 :         else if (g_Console->IsActive() && hotkey == "paste")
     689             :         {
     690           0 :             char* utf8_text = SDL_GetClipboardText();
     691           0 :             if (!utf8_text)
     692           0 :                 return IN_HANDLED;
     693             : 
     694           0 :             std::wstring text = wstring_from_utf8(utf8_text);
     695           0 :             SDL_free(utf8_text);
     696             : 
     697           0 :             for (wchar_t c : text)
     698           0 :                 g_Console->InsertChar(0, c);
     699             : 
     700           0 :             return IN_HANDLED;
     701             :         }
     702             :     }
     703             : 
     704           0 :     if (!g_Console->IsActive())
     705           0 :         return IN_PASS;
     706             : 
     707             :     // In SDL2, we no longer get Unicode wchars via SDL_Keysym
     708             :     // we use text input events instead and they provide UTF-8 chars
     709           0 :     if (ev->ev.type == SDL_TEXTINPUT)
     710             :     {
     711             :         // TODO: this could be more efficient with an interface to insert UTF-8 strings directly
     712           0 :         std::wstring wstr = wstring_from_utf8(ev->ev.text.text);
     713           0 :         for (size_t i = 0; i < wstr.length(); ++i)
     714           0 :             g_Console->InsertChar(0, wstr[i]);
     715           0 :         return IN_HANDLED;
     716             :     }
     717             :     // TODO: text editing events for IME support
     718             : 
     719           0 :     if (ev->ev.type != SDL_KEYDOWN && ev->ev.type != SDL_KEYUP)
     720           0 :         return IN_PASS;
     721             : 
     722           0 :     int sym = ev->ev.key.keysym.sym;
     723             : 
     724             :     // Stop unprintable characters (ctrl+, alt+ and escape).
     725           0 :     if (ev->ev.type == SDL_KEYDOWN && isUnprintableChar(ev->ev.key.keysym) &&
     726           0 :         !HotkeyIsPressed("console.toggle"))
     727             :     {
     728           0 :         g_Console->InsertChar(sym, 0);
     729           0 :         return IN_HANDLED;
     730             :     }
     731             : 
     732             :     // We have a probably printable key - we should return HANDLED so it can't trigger hotkeys.
     733             :     // However, if Ctrl/Meta modifiers are active (or it's escape), just pass it through instead,
     734             :     // assuming that we are indeed trying to trigger hotkeys (e.g. copy/paste).
     735             :     // Also ignore the key if we are trying to toggle the console off.
     736             :     // See also similar logic in CInput.cpp
     737           0 :     if (EventWillFireHotkey(ev, "console.toggle") ||
     738           0 :         g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL] ||
     739           0 :         g_scancodes[SDL_SCANCODE_LGUI] || g_scancodes[SDL_SCANCODE_RGUI])
     740           0 :         return IN_PASS;
     741             : 
     742           0 :     return IN_HANDLED;
     743           3 : }

Generated by: LCOV version 1.13