Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utf8 #311

Merged
merged 12 commits into from
Oct 12, 2023
Merged
22 changes: 19 additions & 3 deletions cpp-terminal/platforms/cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <termios.h>
#endif

#include "cpp-terminal/platforms/file.hpp"

#include <iostream>

Term::Cursor Term::cursor_position()
{
if(Term::Private::in.null()) return Term::Cursor();
Expand All @@ -31,9 +30,26 @@ Term::Cursor Term::cursor_position()
std::string ret;
std::size_t nread{0};
Term::Private::in.lockIO();
// Hack to be sure we can do this all the time "Cooked" or "Raw" mode
::termios actual;
if(!Private::out.null())
if(tcgetattr(Private::out.fd(), &actual) == -1) return Term::Cursor();
::termios raw = actual;
// Put terminal in raw mode
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
// This disables output post-processing, requiring explicit \r\n. We
// keep it enabled, so that in C++, one can still just use std::endl
// for EOL instead of "\r\n".
// raw.c_oflag &= ~(OPOST);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
raw.c_lflag &= ~ISIG;
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if(!Private::out.null()) tcsetattr(Private::out.fd(), TCSAFLUSH, &raw);
Term::Private::out.write(Term::cursor_position_report());
while(nread == 0) ::ioctl(Private::in.fd(), FIONREAD, &nread);
while(nread == 0) { ::ioctl(Private::in.fd(), FIONREAD, &nread); }
ret = Term::Private::in.read();
tcsetattr(Private::out.fd(), TCSAFLUSH, &actual);
Term::Private::in.unlockIO();
try
{
Expand Down
4 changes: 3 additions & 1 deletion cpp-terminal/platforms/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Term::Private::FileHandler::FileHandler(std::recursive_mutex& mutex, const std::
m_file = _fdopen(m_fd, mode.c_str());
}
#else
std::size_t flag{O_ASYNC | O_DSYNC | O_NOCTTY | O_SYNC};
std::size_t flag{O_ASYNC | O_DSYNC | O_NOCTTY | O_SYNC | O_NDELAY};
if(mode.find('r') != std::string::npos) flag |= O_RDONLY;
else if(mode.find('w') != std::string::npos)
flag |= O_WRONLY;
Expand Down Expand Up @@ -169,5 +169,7 @@ std::string Term::Private::InputFileHandler::read()
#endif
}

void Term::Private::FileHandler::flush() { std::fflush(m_file); }

void Term::Private::FileHandler::lockIO() { m_mutex.lock(); }
void Term::Private::FileHandler::unlockIO() { m_mutex.unlock(); }
1 change: 1 addition & 0 deletions cpp-terminal/platforms/file.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class FileHandler
int fd() const;
void lockIO();
void unlockIO();
void flush();
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
FileHandler(FileHandler&&) = delete;
Expand Down
71 changes: 51 additions & 20 deletions cpp-terminal/platforms/terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "cpp-terminal/terminal.hpp"

#include "cpp-terminal/exception.hpp"
#include "cpp-terminal/iostream.hpp"
#include "cpp-terminal/platforms/env.hpp"
#include "cpp-terminal/platforms/file.hpp"

Expand All @@ -29,42 +30,66 @@
#include <termios.h>
#endif

void Term::Terminal::store_and_restore()
void Term::Terminal::set_unset_utf8()
{
static bool enabled{false};
#ifdef _WIN32
static UINT out_code_page{0};
static UINT in_code_page{0};
static DWORD dwOriginalOutMode{0};
static DWORD dwOriginalInMode{0};
#if defined(_WIN32)
static UINT out_code_page{0};
static UINT in_code_page{0};
if(!enabled)
{
out_code_page = GetConsoleOutputCP();
if(out_code_page == 0) throw Term::Exception("GetConsoleOutputCP() failed");
if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Exception("SetConsoleOutputCP(CP_UTF8) failed");
in_code_page = GetConsoleCP();
if(out_code_page == 0) throw Term::Exception("GetConsoleCP() failed");
if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Exception("SetConsoleOutputCP(CP_UTF8) failed");
if(!SetConsoleCP(CP_UTF8)) throw Term::Exception("SetConsoleCP(CP_UTF8) failed");
enabled = true;
}
else
{
if(!SetConsoleOutputCP(out_code_page)) throw Term::Exception("SetConsoleOutputCP(out_code_page) failed");
if(!SetConsoleCP(in_code_page)) throw Term::Exception("SetConsoleCP(in_code_page) failed");
}
#else
if(!enabled)
{
Term::Private::out.write("\033%G");
enabled = true;
}
else
{
// Does not return the original charset but, the default defined by standard ISO 8859-1 (ISO 2022);
Term::Private::out.write("\033%@");
}
#endif
}

void Term::Terminal::store_and_restore()
{
static bool enabled{false};
#ifdef _WIN32
static DWORD dwOriginalOutMode{0};
static DWORD dwOriginalInMode{0};
if(!enabled)
{
if(!GetConsoleMode(Private::out.handle(), &dwOriginalOutMode)) { throw Term::Exception("GetConsoleMode() failed"); }
if(!GetConsoleMode(Private::in.handle(), &dwOriginalInMode)) { throw Term::Exception("GetConsoleMode() failed"); }
dwOriginalInMode |= (ENABLE_EXTENDED_FLAGS | activateFocusEvents() | ENABLE_MOUSE_INPUT);
dwOriginalInMode &= ~ENABLE_QUICK_EDIT_MODE;
DWORD in{(dwOriginalInMode & ~ENABLE_QUICK_EDIT_MODE) | (ENABLE_EXTENDED_FLAGS | activateFocusEvents() | activateMouseEvents())};
DWORD out{dwOriginalOutMode};
if(!m_terminfo.isLegacy())
{
dwOriginalOutMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
dwOriginalInMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
out |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
in |= ENABLE_VIRTUAL_TERMINAL_INPUT;
}
if(!SetConsoleMode(Private::out.handle(), dwOriginalOutMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); }
if(!SetConsoleMode(Private::in.handle(), dwOriginalInMode)) { throw Term::Exception("SetConsoleMode() failed"); }
if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Exception("SetConsoleMode() failed in destructor"); }
if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Exception("SetConsoleMode() failed"); }
enabled = true;
}
else
{
if(!SetConsoleMode(Private::out.handle(), dwOriginalOutMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); }
if(!SetConsoleMode(Private::in.handle(), dwOriginalInMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); }
if(!SetConsoleOutputCP(out_code_page)) throw Term::Exception("SetConsoleOutputCP(out_code_page) failed");
if(!SetConsoleCP(in_code_page)) throw Term::Exception("SetConsoleCP(in_code_page) failed");
enabled = false;
}
#else
Expand All @@ -73,6 +98,13 @@ void Term::Terminal::store_and_restore()
{
if(!Private::out.null())
if(tcgetattr(Private::out.fd(), &orig_termios) == -1) { throw Term::Exception("tcgetattr() failed"); }
termios term = orig_termios;
term.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
term.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
term.c_cflag &= ~CSIZE; // Clear all the size bits, then use one of the statements below
term.c_cflag |= CS8; // 8 bits per byte (most common)
if(!Private::out.null())
if(tcsetattr(Private::out.fd(), TCSAFLUSH, &term) == -1) { throw Term::Exception("tcsetattr() failed in destructor"); }
enabled = true;
}
else
Expand All @@ -87,7 +119,7 @@ void Term::Terminal::store_and_restore()
int Term::Terminal::activateMouseEvents()
{
#if defined(_WIN32)
return 0; //FIXME
return ENABLE_MOUSE_INPUT;
#else
return Term::Private::out.write("\033[?1002h\033[?1003h\033[?1006h");
#endif
Expand All @@ -96,7 +128,7 @@ int Term::Terminal::activateMouseEvents()
int Term::Terminal::desactivateMouseEvents()
{
#if defined(_WIN32)
return 0; //FIXME
return ENABLE_MOUSE_INPUT;
#else
return Term::Private::out.write("\033[?1003l\033[?1006l");
#endif
Expand Down Expand Up @@ -135,8 +167,8 @@ void Term::Terminal::setBadStateReturnCode()

void Term::Terminal::setRawMode()
{
#ifdef _WIN32
DWORD flags = {0};
#if defined(_WIN32)
DWORD flags{0};
if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Exception("GetConsoleMode() failed"); }
if(m_options.has(Option::NoSignalKeys)) { flags &= ~ENABLE_PROCESSED_INPUT; }
flags &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
Expand All @@ -152,7 +184,6 @@ void Term::Terminal::setRawMode()
// keep it enabled, so that in C++, one can still just use std::endl
// for EOL instead of "\r\n".
// raw.c_oflag &= ~(OPOST);
raw.c_cflag |= CS8;
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
if(m_options.has(Option::NoSignalKeys)) { raw.c_lflag &= ~ISIG; }
raw.c_cc[VMIN] = 1;
Expand Down
7 changes: 6 additions & 1 deletion cpp-terminal/platforms/terminfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,21 @@ void Term::Terminfo::setANSIEscapeCode()
#endif
}

void Term::Terminfo::setUTF8()
void Term::Terminfo::checkUTF8()
{
#if defined(_WIN32)
(GetConsoleOutputCP() == CP_UTF8 && GetConsoleCP() == CP_UTF8) ? m_UTF8 = true : m_UTF8 = false;
#else
Term::Cursor cursor_before{Term::cursor_position()};
Term::Private::out.write("\xe2\x82\xac"); // € 3bits in utf8 one character
std::string read{Term::Private::in.read()};
Term::Cursor cursor_after{Term::cursor_position()};
std::size_t moved{cursor_after.column() - cursor_before.column()};
if(moved == 1) m_UTF8 = true;
else
m_UTF8 = false;
for(std::size_t i = 0; i != moved; ++i) Term::Private::out.write("\b \b");
#endif
}

bool Term::Terminfo::hasUTF8() { return m_UTF8; }
13 changes: 8 additions & 5 deletions cpp-terminal/terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,11 @@ Term::Terminal::Terminal()
Term::Private::Sigwinch::blockSigwinch();
setBadStateReturnCode();
attachConsole();
set_unset_utf8();
store_and_restore();
activateMouseEvents();
activateFocusEvents();
setRawMode();
m_terminfo.setUTF8();
store_and_restore();
store_and_restore();
m_terminfo.checkUTF8();
}

bool Term::Terminal::supportUTF8() { return m_terminfo.hasUTF8(); }
Expand All @@ -67,11 +65,16 @@ Term::Terminal::~Terminal()
{
try
{
// For windows
Term::cerr << std::flush;
Term::clog << std::flush;
Term::cout << std::flush;
if(m_options.has(Option::ClearScreen)) Term::Private::out.write(clear_buffer() + style(Style::RESET) + cursor_move(1, 1) + screen_load());
if(m_options.has(Option::NoCursor)) Term::Private::out.write(cursor_on());
store_and_restore();
set_unset_utf8();
desactivateFocusEvents();
desactivateMouseEvents();
store_and_restore();
detachConsole();
}
catch(const Term::Exception& e)
Expand Down
1 change: 1 addition & 0 deletions cpp-terminal/terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Terminal
int desactivateMouseEvents();
int activateFocusEvents();
int desactivateFocusEvents();
void set_unset_utf8();
bool has_allocated_console{false};
Term::Terminfo m_terminfo;
Term::Options m_options;
Expand Down
2 changes: 1 addition & 1 deletion cpp-terminal/terminfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Terminfo
bool hasANSIEscapeCode() const;
bool isLegacy() const;
bool hasUTF8();
void setUTF8();
void checkUTF8();

private:
void setANSIEscapeCode();
Expand Down
2 changes: 1 addition & 1 deletion examples/utf8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,6 @@ int main()
<< "║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n"
<< "║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n"
<< "╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n"
<< " ▝▀▘▙▄▟\n";
<< " ▝▀▘▙▄▟\n"<<std::flush;
// clang-format on
}
Loading