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: 2 353 0.6 %
Date: 2022-06-14 00:41:00 Functions: 1 26 3.8 %

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

Generated by: LCOV version 1.13