C++ SDK for MANUS Core 2.0
Loading...
Searching...
No Matches
ClientPlatformSpecific.cpp
Go to the documentation of this file.
2
3// signal types
4#include <csignal>
5// std::filesystem
6#include <filesystem>
7// std::*fstream
8#include <fstream>
9// CoreSdk_ShutDown
10#include "ManusSDK.h"
11// std::map
12#include <map>
13// Ncurses terminal functions.
14#include <ncursesw/ncurses.h>
15// Termios terminal functions.
16#include <termios.h>
17// spdlog
18#include "spdlog/spdlog.h"
19
23#define CALL_DEFAULT_SIGNAL_HANDLER(p_SignalType) \
24 /* Reset the handler for this signal to the default. */ \
25 signal(p_SignalType, SIG_DFL); \
26 /* Re-raise this signal, causing the normal handler to run. */ \
27 raise(p_SignalType);
28
30
35static void HandleTerminationSignal(int p_Parameter)
36{
37 spdlog::error(
38 "Termination signal sent with parameter {}.",
39 p_Parameter);
40
42
44}
45
48static void HandleInterruptSignal(int p_Parameter)
49{
50 spdlog::error(
51 "Interrupt signal sent with parameter {}.",
52 p_Parameter);
53
55
57}
58
61static void HandleQuitSignal(int p_Parameter)
62{
63 spdlog::error(
64 "Quit signal sent with parameter {}.",
65 p_Parameter);
66
68
70}
71
76static void HandleHangupSignal(int p_Parameter)
77{
78 spdlog::error(
79 "Hang-up signal sent with parameter {}.",
80 p_Parameter);
81
83
85}
86
88static bool InitializeNcurses(void)
89{
90 const WINDOW* const t_Window = initscr();
91 if (!t_Window)
92 {
93 spdlog::error("Failed to initialise the screen.");
94
95 return false;
96 }
97
98 // Don't buffer input until a newline or carriage return is typed.
99 if (cbreak() != OK)
100 {
101 spdlog::error("Failed to make input unbuffered.");
102
103 return false;
104 }
105
106 // Don't echo input.
107 if (noecho() != OK)
108 {
109 spdlog::error("Failed to disable input echoing.");
110
111 return false;
112 }
113
114 // Don't make newlines when the return key is pressed.
115 if (nonl() != OK)
116 {
117 spdlog::error("Failed to disable newlines.");
118
119 return false;
120 }
121
122 // Do not flush the screen when interrupt/break/quit is pressed.
123 if (intrflush(stdscr, FALSE) != OK)
124 {
125 spdlog::error("Failed to disable screen flushing.");
126
127 return false;
128 }
129
130 // Make getch non-blocking.
131 if (nodelay(stdscr, TRUE) != OK)
132 {
133 spdlog::error("Failed to make nodelay non-blocking.");
134
135 return false;
136 }
137
138 // Enable handling the keypad ("function keys" like the arrow keys).
139 if (keypad(stdscr, TRUE) != OK)
140 {
141 spdlog::error("Failed to enable keypad input.");
142
143 return false;
144 }
145
146 return true;
147}
148
150static bool InitializeTermios(void)
151{
152 // For some reason, printf and spdlog strings require a carriage return
153 // in addition to a newline after running initscr().
154 // This code sets the "output modes" flag to treat newline characters
155 // as newline+carriage-return characters.
156 // https://arstechnica.com/civis/viewtopic.php?t=70699
157 termios t_Settings;
158 if (tcgetattr(STDIN_FILENO, &t_Settings) != 0)
159 {
160 spdlog::error("Failed to get Termios settings.");
161
162 return false;
163 }
164
165 t_Settings.c_oflag |= ONLCR;
166 if (tcsetattr(0, TCSANOW, &t_Settings) != 0)
167 {
168 spdlog::error("Failed to set Termios settings.");
169
170 return false;
171 }
172
173 return true;
174}
175
177static bool SetUpSignalHandlers(void)
178{
179 {
180 const __sighandler_t t_OldTerminationHandler = signal(
181 SIGTERM,
182 HandleTerminationSignal);
183 if (t_OldTerminationHandler == SIG_ERR)
184 {
185 spdlog::error("Failed to set termination signal handler.");
186 return false;
187 }
188 }
189
190 {
191 const __sighandler_t t_OldInterruptHandler = signal(
192 SIGINT,
193 HandleInterruptSignal);
194 if (t_OldInterruptHandler == SIG_ERR)
195 {
196 spdlog::error("Failed to set interrupt signal handler.");
197 return false;
198 }
199 }
200
201 {
202 const __sighandler_t t_OldQuitHandler = signal(
203 SIGQUIT,
204 HandleQuitSignal);
205 if (t_OldQuitHandler == SIG_ERR)
206 {
207 spdlog::error("Failed to set quit signal handler.");
208 return false;
209 }
210 }
211
212 {
213 const __sighandler_t t_OldHangupHandler = signal(
214 SIGHUP,
215 HandleHangupSignal);
216 if (t_OldHangupHandler == SIG_ERR)
217 {
218 spdlog::error("Failed to set hang-up signal handler.");
219 return false;
220 }
221 }
222
223 return true;
224}
225
229{
230public:
232 void Update(void)
233 {
234 // Reset the state of the last update.
235 for (
236 InputMap_t::iterator t_Key = m_PressedLastUpdate.begin();
237 t_Key != m_PressedLastUpdate.end();
238 t_Key++)
239 {
240 t_Key->second = false;
241 }
242
243 // Copy the current state to the last update's state, and clear the
244 // current state.
245 for (
246 InputMap_t::iterator t_Key = m_CurrentlyPressed.begin();
247 t_Key != m_CurrentlyPressed.end();
248 t_Key++)
249 {
250 m_PressedLastUpdate[t_Key->first] =
251 t_Key->second;
252 t_Key->second = false;
253 }
254
255 // Get the new state.
256 int t_Ch = getch();
257 while (t_Ch != ERR)
258 {
259 if (t_Ch >= 'a' && t_Ch <= 'z')
260 {
261 // Unlike with Windows' GetAsyncKeyState(), upper case and
262 // lower case characters have different key numbers with
263 // getch().
264 // Since all WasKeyPressed calls (as of writing this) use
265 // upper case, lower case keys need to be converted to work
266 // on Linux.
267 // Note that this does break the ability to check for lower
268 // case key presses.
269 t_Ch = toupper(t_Ch);
270 }
271
272 m_CurrentlyPressed[t_Ch] = true;
273
274 t_Ch = getch();
275 }
276 }
277
281 bool GetKey(const int p_Key)
282 {
283 bool t_IsPressed = IsPressed(p_Key);
284 m_PreviousKeyState[p_Key] = t_IsPressed;
285
286 return t_IsPressed;
287 }
288
292 bool GetKeyDown(const int p_Key)
293 {
294 const bool t_IsPressed = IsPressed(p_Key);
295
296 const auto t_PreviousState = m_PreviousKeyState.find(p_Key);
297 const bool t_PreviousValue =
298 t_PreviousState == m_PreviousKeyState.end()
299 ? false
300 : t_PreviousState->second;
301
302 const bool t_Down = t_IsPressed && !t_PreviousValue;
303 m_PreviousKeyState[p_Key] = t_Down;
304
305 return t_Down;
306 }
307
311 bool GetKeyUp(const int p_Key)
312 {
313 const bool t_IsPressed = IsPressed(p_Key);
314
315 const auto t_PreviousState = m_PreviousKeyState.find(p_Key);
316 const bool t_PreviousValue =
317 t_PreviousState == m_PreviousKeyState.end()
318 ? false
319 : t_PreviousState->second;
320
321 const bool t_Up = !t_IsPressed && t_PreviousValue;
322 m_PreviousKeyState[p_Key] = t_Up;
323
324 return t_Up;
325 }
326
327private:
331 bool IsPressed(const int p_Key) const
332 {
333 auto t_CurrentlyPressed = m_CurrentlyPressed.find(p_Key);
334 if (t_CurrentlyPressed == m_CurrentlyPressed.end())
335 {
336 return false;
337 }
338
339 return t_CurrentlyPressed->second;
340 }
341
345 bool WasJustPressed(const int p_Key) const
346 {
347 return !WasPressedLastUpdate(p_Key) && IsPressed(p_Key);
348 }
349
353 bool WasJustReleased(const int p_Key) const
354 {
355 return WasPressedLastUpdate(p_Key) && !IsPressed(p_Key);
356 }
357
359 bool WasPressedLastUpdate(const int p_Key) const
360 {
361 auto t_StateLastUpdate = m_PressedLastUpdate.find(p_Key);
362 if (t_StateLastUpdate == m_PressedLastUpdate.end())
363 {
364 return false;
365 }
366
367 return t_StateLastUpdate->second;
368 }
369
370 typedef std::map<int, bool> InputMap_t;
373 // The Windows GetKey* functions only update the key state when a GetKey*
374 // function gets called. To make the Linux input work the same way, this
375 // map is used.
377};
378
379static ClientInput g_Input;
380
382{
383 const bool t_NcursesResult = InitializeNcurses();
384 const bool t_TermiosResult = InitializeTermios();
385 const bool t_SignalResult = SetUpSignalHandlers();
386
387 return t_NcursesResult && t_TermiosResult && t_SignalResult;
388}
389
391{
392 // Ncurses.
393 endwin();
394
395 return true;
396}
397
399{
400 g_Input.Update();
401}
402
404 char* const p_Target,
405 const size_t p_MaxLengthThatWillFitInTarget,
406 const std::string& p_Source)
407{
408 if (!p_Target)
409 {
410 SPDLOG_ERROR(
411 "Tried to copy a string, but the target was null. The string was \"{}\".",
412 p_Source.c_str());
413
414 return false;
415 }
416
417 if (p_MaxLengthThatWillFitInTarget == 0)
418 {
419 SPDLOG_ERROR(
420 "Tried to copy a string, but the target's size is zero. The string was \"{}\".",
421 p_Source.c_str());
422
423 return false;
424 }
425
426 if (p_MaxLengthThatWillFitInTarget <= p_Source.length())
427 {
428 SPDLOG_ERROR(
429 "Tried to copy a string that was longer than {} characters, which makes it too big for its target buffer. The string was \"{}\".",
430 p_MaxLengthThatWillFitInTarget,
431 p_Source.c_str());
432
433 return false;
434 }
435
436 strcpy(p_Target, p_Source.c_str());
437
438 return true;
439}
440
442 const short int p_ConsoleWidth,
443 const short int p_ConsoleHeight,
444 const short int p_ConsoleScrollback)
445{
446 // https://apple.stackexchange.com/questions/33736/can-a-terminal-window-be-resized-with-a-terminal-command/47841#47841
447 // Use a control sequence to resize the window.
448 // Seems to be supported by the default terminal used in Gnome, as well
449 // as Mac OS.
450 // \\e[ -> ASCII ESC character (number 27, or 0x1B)
451 // -> control sequence introducer
452 // 8; -> resize the window
453 // y;xt -> The first number is the height, the second the width.
454 // Scrollback can't and doesn't need to be set here for Linux.
455 printf("\e[8;%d;%dt", p_ConsoleHeight, p_ConsoleWidth);
456
457 ClearConsole();
458
459 // None of the ncurses functions for resizing the terminal actually
460 // seem to do anything.
461 /*if (resizeterm(180, 180) != OK)
462 {
463 return false;
464 }*/
465
466 return true;
467}
468
470 const int p_ConsoleCurrentOffset)
471{
472 printf("\e[%d;1H", p_ConsoleCurrentOffset);
473}
474
476{
477 // https://stackoverflow.com/questions/4062045/clearing-terminal-in-linux-with-c-code
478 // Use a control sequence to clear the terminal and move the cursor.
479 // Seems to be supported by the default terminal used in Gnome,
480 // as well as Mac OS.
481 // \\e[ -> ASCII ESC character (number 27, or 0x1B) -> control sequence introducer
482 // 2 -> the entire screen
483 // J -> clear the screen
484 //printf("\e[2J\n");
485
486 // Move the cursor to row 1 column 1.
487 //printf("\e[1;1H\n");
488
489 if (clear() != OK)
490 {
491 spdlog::error("Failed to clear the screen.");
492 }
493
494 refresh();
495}
496
498{
499 return g_Input.GetKey(p_Key);
500}
501
503{
504 return g_Input.GetKeyDown(p_Key);
505}
506
508{
509 return g_Input.GetKeyUp(p_Key);
510}
511
513{
514 const char* const t_Xdg = getenv("XDG_DOCUMENTS_DIR");
515
516 // Backup - the documents folder is usually going to be in $HOME/Documents.
517 const char* const t_Home = getenv("HOME");
518 if (!t_Xdg && !t_Home)
519 {
520 return std::string("");
521 }
522
523 const std::string t_DocumentsDir =
524 (!t_Xdg || strlen(t_Xdg) == 0)
525 ? std::string(t_Home) + std::string("/Documents")
526 : std::string(t_Xdg);
527
528 return t_DocumentsDir;
529}
530
532 std::string p_Path_UTF8)
533{
534 return std::ifstream(p_Path_UTF8, std::ifstream::binary);
535}
536
538 std::string p_Path_UTF8)
539{
540 return std::ofstream(p_Path_UTF8, std::ofstream::binary);
541}
542
544{
545 return std::filesystem::exists(p_Path_UTF8);
546}
547
549 std::string p_Path_UTF8)
550{
551 if (!DoesFolderOrFileExist(p_Path_UTF8))
552 {
553 std::filesystem::create_directory(p_Path_UTF8);
554 }
555}
#define CALL_DEFAULT_SIGNAL_HANDLER(p_SignalType)
Reset a signal handler to its default, and then call it. For signal types and explanation,...
Handles keyboard input. Requires things to be set up with Ncurses and Termios.
InputMap_t m_CurrentlyPressed
bool IsPressed(const int p_Key) const
Get the key's current state. Note that unlike GetKey(), this does not store the result for use in the...
bool WasJustReleased(const int p_Key) const
Was this key released since the last input update? Note that unlike GetKeyUp(), this function will re...
InputMap_t m_PreviousKeyState
InputMap_t m_PressedLastUpdate
bool GetKeyDown(const int p_Key)
Was this key pressed since the last check? Note that unlike WasJustPressed(), this checks if the key ...
std::map< int, bool > InputMap_t
bool GetKeyUp(const int p_Key)
Was this key released since the last check? Note that unlike WasJustReleased(), this checks if the ke...
bool GetKey(const int p_Key)
Get the key's current state. Note that unlike IsPressed(), this also stores the result for use in the...
bool WasJustPressed(const int p_Key) const
Was this key pressed since the last input update? Note that unlike GetKeyDown(), this function will r...
bool WasPressedLastUpdate(const int p_Key) const
Was this key pressed the previous input update?
void Update(void)
Update the state of the keyboard.
CORESDK_API SDKReturnCode CoreSdk_ShutDown()
Shut down the wrapper. This needs to be called last.
void ApplyConsolePosition(const int p_ConsoleCurrentOffset)
Set the current console position. This handles the platform-specific part of advancing the console po...
static void ClearConsole(void)
Clear the console window.
void UpdateInput(void)
Update the current keyboard state.
bool PlatformSpecificInitialization(void)
Initialise things only needed for this platform.
bool GetKeyDown(const int p_Key)
Returns true first time it is called when key is pressed.
bool GetKeyUp(const int p_Key)
Returns true first time it is called when key is released.
static bool CopyString(char *const p_Target, const size_t p_MaxLengthThatWillFitInTarget, const std::string &p_Source)
Copy the given string into the given target.
bool ResizeWindow(const short int p_ConsoleWidth, const short int p_ConsoleHeight, const short int p_ConsoleScrollback)
Resize the window, so the log messages will fit. Make sure not to enter illegal sizes in here or SetC...
bool DoesFolderOrFileExist(std::string p_Path_UTF8)
Check if the given folder or file exists. The folder path given should be in UTF-8 format.
bool PlatformSpecificShutdown(void)
Shut down things only needed for this platform.
bool GetKey(const int p_Key)
Gets key's current state. True is pressed false is not pressed.
std::ofstream GetOutputFileStream(std::string p_Path_UTF8)
Get an output stream for the given file. The file's path should be in UTF-8 format.
std::string GetDocumentsDirectoryPath_UTF8(void)
Get the path to the user's Documents folder. The string should be in UTF-8 format.
void CreateFolderIfItDoesNotExist(std::string p_Path_UTF8)
Create the given folder if it does not exist. The folder path given should be in UTF-8 format.
static const std::string s_SlashForFilesystemPath
The slash character that is used in the filesystem.
std::ifstream GetInputFileStream(std::string p_Path_UTF8)
Get an input stream for the given file. The file's path should be in UTF-8 format.