mirror of
https://github.com/MCLx86/xtreemtest.git
synced 2026-04-15 21:38:49 +02:00
Initial commit
This commit is contained in:
457
src/terminal_chat_console.cpp
Normal file
457
src/terminal_chat_console.cpp
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2015 est31 <MTest31@outlook.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "config.h"
|
||||
#if USE_CURSES
|
||||
#include "version.h"
|
||||
#include "terminal_chat_console.h"
|
||||
#include "porting.h"
|
||||
#include "settings.h"
|
||||
#include "util/numeric.h"
|
||||
#include "util/string.h"
|
||||
#include "chat_interface.h"
|
||||
|
||||
TerminalChatConsole g_term_console;
|
||||
|
||||
// include this last to avoid any conflicts
|
||||
// (likes to set macros to common names, conflicting various stuff)
|
||||
#if CURSES_HAVE_NCURSESW_NCURSES_H
|
||||
#include <ncursesw/ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSESW_CURSES_H
|
||||
#include <ncursesw/curses.h>
|
||||
#elif CURSES_HAVE_CURSES_H
|
||||
#include <curses.h>
|
||||
#elif CURSES_HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSES_NCURSES_H
|
||||
#include <ncurses/ncurses.h>
|
||||
#elif CURSES_HAVE_NCURSES_CURSES_H
|
||||
#include <ncurses/curses.h>
|
||||
#endif
|
||||
|
||||
// Some functions to make drawing etc position independent
|
||||
static bool reformat_backend(ChatBackend *backend, int rows, int cols)
|
||||
{
|
||||
if (rows < 2)
|
||||
return false;
|
||||
backend->reformat(cols, rows - 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void move_for_backend(int row, int col)
|
||||
{
|
||||
move(row + 1, col);
|
||||
}
|
||||
|
||||
void TerminalChatConsole::initOfCurses()
|
||||
{
|
||||
initscr();
|
||||
cbreak(); //raw();
|
||||
noecho();
|
||||
keypad(stdscr, TRUE);
|
||||
nodelay(stdscr, TRUE);
|
||||
timeout(100);
|
||||
|
||||
// To make esc not delay up to one second. According to the internet,
|
||||
// this is the value vim uses, too.
|
||||
set_escdelay(25);
|
||||
|
||||
getmaxyx(stdscr, m_rows, m_cols);
|
||||
m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
|
||||
}
|
||||
|
||||
void TerminalChatConsole::deInitOfCurses()
|
||||
{
|
||||
endwin();
|
||||
}
|
||||
|
||||
void *TerminalChatConsole::run()
|
||||
{
|
||||
BEGIN_DEBUG_EXCEPTION_HANDLER
|
||||
|
||||
std::cout << "========================" << std::endl;
|
||||
std::cout << "Begin log output over terminal"
|
||||
<< " (no stdout/stderr backlog during that)" << std::endl;
|
||||
// Make the loggers to stdout/stderr shut up.
|
||||
// Go over our own loggers instead.
|
||||
LogLevelMask err_mask = g_logger.removeOutput(&stderr_output);
|
||||
LogLevelMask out_mask = g_logger.removeOutput(&stdout_output);
|
||||
|
||||
g_logger.addOutput(&m_log_output);
|
||||
|
||||
// Inform the server of our nick
|
||||
m_chat_interface->command_queue.push_back(
|
||||
new ChatEventNick(CET_NICK_ADD, m_nick));
|
||||
|
||||
{
|
||||
// Ensures that curses is deinitialized even on an exception being thrown
|
||||
CursesInitHelper helper(this);
|
||||
|
||||
while (!stopRequested()) {
|
||||
|
||||
int ch = getch();
|
||||
if (stopRequested())
|
||||
break;
|
||||
|
||||
step(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_kill_requested)
|
||||
*m_kill_requested = true;
|
||||
|
||||
g_logger.removeOutput(&m_log_output);
|
||||
g_logger.addOutputMasked(&stderr_output, err_mask);
|
||||
g_logger.addOutputMasked(&stdout_output, out_mask);
|
||||
|
||||
std::cout << "End log output over terminal"
|
||||
<< " (no stdout/stderr backlog during that)" << std::endl;
|
||||
std::cout << "========================" << std::endl;
|
||||
|
||||
END_DEBUG_EXCEPTION_HANDLER
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TerminalChatConsole::typeChatMessage(const std::wstring &msg)
|
||||
{
|
||||
// Discard empty line
|
||||
if (msg.empty())
|
||||
return;
|
||||
|
||||
// Send to server
|
||||
m_chat_interface->command_queue.push_back(
|
||||
new ChatEventChat(m_nick, msg));
|
||||
|
||||
// Print if its a command (gets eaten by server otherwise)
|
||||
if (msg[0] == L'/') {
|
||||
m_chat_backend.addMessage(L"", (std::wstring)L"Issued command: " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::handleInput(int ch, bool &complete_redraw_needed)
|
||||
{
|
||||
ChatPrompt &prompt = m_chat_backend.getPrompt();
|
||||
// Helpful if you want to collect key codes that aren't documented
|
||||
/*if (ch != ERR) {
|
||||
m_chat_backend.addMessage(L"",
|
||||
(std::wstring)L"Pressed key " + utf8_to_wide(
|
||||
std::string(keyname(ch)) + " (code " + itos(ch) + ")"));
|
||||
complete_redraw_needed = true;
|
||||
}//*/
|
||||
|
||||
// All the key codes below are compatible to xterm
|
||||
// Only add new ones if you have tried them there,
|
||||
// to ensure compatibility with not just xterm but the wide
|
||||
// range of terminals that are compatible to xterm.
|
||||
|
||||
switch (ch) {
|
||||
case ERR: // no input
|
||||
break;
|
||||
case 27: // ESC
|
||||
// Toggle ESC mode
|
||||
m_esc_mode = !m_esc_mode;
|
||||
break;
|
||||
case KEY_PPAGE:
|
||||
m_chat_backend.scrollPageUp();
|
||||
complete_redraw_needed = true;
|
||||
break;
|
||||
case KEY_NPAGE:
|
||||
m_chat_backend.scrollPageDown();
|
||||
complete_redraw_needed = true;
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
case '\r':
|
||||
case '\n': {
|
||||
prompt.addToHistory(prompt.getLine());
|
||||
typeChatMessage(prompt.replace(L""));
|
||||
break;
|
||||
}
|
||||
case KEY_UP:
|
||||
prompt.historyPrev();
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
prompt.historyNext();
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
// Left pressed
|
||||
// move character to the left
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 545:
|
||||
// Ctrl-Left pressed
|
||||
// move word to the left
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
// Right pressed
|
||||
// move character to the right
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 560:
|
||||
// Ctrl-Right pressed
|
||||
// move word to the right
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case KEY_HOME:
|
||||
// Home pressed
|
||||
// move to beginning of line
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case KEY_END:
|
||||
// End pressed
|
||||
// move to end of line
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_MOVE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case '\b':
|
||||
case 127:
|
||||
// Backspace pressed
|
||||
// delete character to the left
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case KEY_DC:
|
||||
// Delete pressed
|
||||
// delete character to the right
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_CHARACTER);
|
||||
break;
|
||||
case 519:
|
||||
// Ctrl-Delete pressed
|
||||
// delete word to the right
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_WORD);
|
||||
break;
|
||||
case 21:
|
||||
// Ctrl-U pressed
|
||||
// kill line to left end
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_LEFT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case 11:
|
||||
// Ctrl-K pressed
|
||||
// kill line to right end
|
||||
prompt.cursorOperation(
|
||||
ChatPrompt::CURSOROP_DELETE,
|
||||
ChatPrompt::CURSOROP_DIR_RIGHT,
|
||||
ChatPrompt::CURSOROP_SCOPE_LINE);
|
||||
break;
|
||||
case KEY_TAB:
|
||||
// Tab pressed
|
||||
// Nick completion
|
||||
prompt.nickCompletion(m_nicks, false);
|
||||
break;
|
||||
default:
|
||||
// Add character to the prompt,
|
||||
// assuming UTF-8.
|
||||
if (IS_UTF8_MULTB_START(ch)) {
|
||||
m_pending_utf8_bytes.append(1, (char)ch);
|
||||
m_utf8_bytes_to_wait += UTF8_MULTB_START_LEN(ch) - 1;
|
||||
} else if (m_utf8_bytes_to_wait != 0) {
|
||||
m_pending_utf8_bytes.append(1, (char)ch);
|
||||
m_utf8_bytes_to_wait--;
|
||||
if (m_utf8_bytes_to_wait == 0) {
|
||||
std::wstring w = utf8_to_wide(m_pending_utf8_bytes);
|
||||
m_pending_utf8_bytes = "";
|
||||
// hopefully only one char in the wstring...
|
||||
for (size_t i = 0; i < w.size(); i++) {
|
||||
prompt.input(w.c_str()[i]);
|
||||
}
|
||||
}
|
||||
} else if (IS_ASCII_PRINTABLE_CHAR(ch)) {
|
||||
prompt.input(ch);
|
||||
} else {
|
||||
// Silently ignore characters we don't handle
|
||||
|
||||
//warningstream << "Pressed invalid character '"
|
||||
// << keyname(ch) << "' (code " << itos(ch) << ")" << std::endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::step(int ch)
|
||||
{
|
||||
bool complete_redraw_needed = false;
|
||||
|
||||
// empty queues
|
||||
while (!m_chat_interface->outgoing_queue.empty()) {
|
||||
ChatEvent *evt = m_chat_interface->outgoing_queue.pop_frontNoEx();
|
||||
switch (evt->type) {
|
||||
case CET_NICK_REMOVE:
|
||||
m_nicks.remove(((ChatEventNick *)evt)->nick);
|
||||
break;
|
||||
case CET_NICK_ADD:
|
||||
m_nicks.push_back(((ChatEventNick *)evt)->nick);
|
||||
break;
|
||||
case CET_CHAT:
|
||||
complete_redraw_needed = true;
|
||||
// This is only used for direct replies from commands
|
||||
// or for lua's print() functionality
|
||||
m_chat_backend.addMessage(L"", ((ChatEventChat *)evt)->evt_msg);
|
||||
break;
|
||||
case CET_TIME_INFO:
|
||||
ChatEventTimeInfo *tevt = (ChatEventTimeInfo *)evt;
|
||||
m_game_time = tevt->game_time;
|
||||
m_time_of_day = tevt->time;
|
||||
};
|
||||
delete evt;
|
||||
}
|
||||
while (!m_log_output.queue.empty()) {
|
||||
complete_redraw_needed = true;
|
||||
std::pair<LogLevel, std::string> p = m_log_output.queue.pop_frontNoEx();
|
||||
if (p.first > m_log_level)
|
||||
continue;
|
||||
|
||||
std::wstring error_message = utf8_to_wide(Logger::getLevelLabel(p.first));
|
||||
if (!g_settings->getBool("disable_escape_sequences")) {
|
||||
error_message = std::wstring(L"\x1b(c@red)").append(error_message)
|
||||
.append(L"\x1b(c@white)");
|
||||
}
|
||||
m_chat_backend.addMessage(error_message, utf8_to_wide(p.second));
|
||||
}
|
||||
|
||||
// handle input
|
||||
if (!m_esc_mode) {
|
||||
handleInput(ch, complete_redraw_needed);
|
||||
} else {
|
||||
switch (ch) {
|
||||
case ERR: // no input
|
||||
break;
|
||||
case 27: // ESC
|
||||
// Toggle ESC mode
|
||||
m_esc_mode = !m_esc_mode;
|
||||
break;
|
||||
case 'L':
|
||||
m_log_level--;
|
||||
m_log_level = MYMAX(m_log_level, LL_NONE + 1); // LL_NONE isn't accessible
|
||||
break;
|
||||
case 'l':
|
||||
m_log_level++;
|
||||
m_log_level = MYMIN(m_log_level, LL_MAX - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// was there a resize?
|
||||
int xn, yn;
|
||||
getmaxyx(stdscr, yn, xn);
|
||||
if (xn != m_cols || yn != m_rows) {
|
||||
m_cols = xn;
|
||||
m_rows = yn;
|
||||
m_can_draw_text = reformat_backend(&m_chat_backend, m_rows, m_cols);
|
||||
complete_redraw_needed = true;
|
||||
}
|
||||
|
||||
// draw title
|
||||
move(0, 0);
|
||||
clrtoeol();
|
||||
addstr(PROJECT_NAME_C);
|
||||
addstr(" ");
|
||||
addstr(g_version_hash);
|
||||
|
||||
u32 minutes = m_time_of_day % 1000;
|
||||
u32 hours = m_time_of_day / 1000;
|
||||
minutes = (float)minutes / 1000 * 60;
|
||||
|
||||
if (m_game_time)
|
||||
printw(" | Game %" PRIu64 " Time of day %02d:%02d ",
|
||||
m_game_time, hours, minutes);
|
||||
|
||||
// draw text
|
||||
if (complete_redraw_needed && m_can_draw_text)
|
||||
draw_text();
|
||||
|
||||
// draw prompt
|
||||
if (!m_esc_mode) {
|
||||
// normal prompt
|
||||
ChatPrompt& prompt = m_chat_backend.getPrompt();
|
||||
std::string prompt_text = wide_to_utf8(prompt.getVisiblePortion());
|
||||
move(m_rows - 1, 0);
|
||||
clrtoeol();
|
||||
addstr(prompt_text.c_str());
|
||||
// Draw cursor
|
||||
s32 cursor_pos = prompt.getVisibleCursorPosition();
|
||||
if (cursor_pos >= 0) {
|
||||
move(m_rows - 1, cursor_pos);
|
||||
}
|
||||
} else {
|
||||
// esc prompt
|
||||
move(m_rows - 1, 0);
|
||||
clrtoeol();
|
||||
printw("[ESC] Toggle ESC mode |"
|
||||
" [CTRL+C] Shut down |"
|
||||
" (L) in-, (l) decrease loglevel %s",
|
||||
Logger::getLevelLabel((LogLevel) m_log_level).c_str());
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void TerminalChatConsole::draw_text()
|
||||
{
|
||||
ChatBuffer& buf = m_chat_backend.getConsoleBuffer();
|
||||
for (u32 row = 0; row < buf.getRows(); row++) {
|
||||
move_for_backend(row, 0);
|
||||
clrtoeol();
|
||||
const ChatFormattedLine& line = buf.getFormattedLine(row);
|
||||
if (line.fragments.empty())
|
||||
continue;
|
||||
for (const ChatFormattedFragment &fragment : line.fragments) {
|
||||
addstr(wide_to_utf8(fragment.text.getString()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalChatConsole::stopAndWaitforThread()
|
||||
{
|
||||
clearKillStatus();
|
||||
stop();
|
||||
wait();
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user