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 : }
|