cpp-terminal 1.0.0
Small C++ library for writing multiplatform terminal applications
Loading...
Searching...
No Matches
terminal_impl.cpp
Go to the documentation of this file.
1/*
2* cpp-terminal
3* C++ library for writing multi-platform terminal applications.
4*
5* SPDX-FileCopyrightText: 2019-2024 cpp-terminal
6*
7* SPDX-License-Identifier: MIT
8*/
9
14//#include "cpp-terminal/private/signals.hpp"
17
18#if defined(_WIN32)
19 #include <io.h>
20 #pragma warning(push)
21 #pragma warning(disable : 4668)
22 #include <windows.h>
23 #pragma warning(pop)
24 #if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING)
25 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
26 #endif
27 #if !defined(DISABLE_NEWLINE_AUTO_RETURN)
28 #define DISABLE_NEWLINE_AUTO_RETURN 0x0008
29 #endif
30 #if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT)
31 #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
32 #endif
33#else
34 #include <termios.h>
35#endif
36
38{
39 static bool enabled{false};
40#if defined(_WIN32)
41 static UINT out_code_page{0};
42 static UINT in_code_page{0};
43 if(!enabled)
44 {
45 if((out_code_page = GetConsoleOutputCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleOutputCP()");
46 if(!SetConsoleOutputCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(CP_UTF8)");
47 if((in_code_page = GetConsoleCP()) == 0) throw Term::Private::WindowsException(GetLastError(), "GetConsoleCP()");
48 if(!SetConsoleCP(CP_UTF8)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(CP_UTF8)");
49 enabled = true;
50 }
51 else
52 {
53 if(!SetConsoleOutputCP(out_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleOutputCP(out_code_page)");
54 if(!SetConsoleCP(in_code_page)) throw Term::Private::WindowsException(GetLastError(), "SetConsoleCP(in_code_page)");
55 }
56#else
57 if(!enabled)
58 {
59 const Term::Cursor cursor_before{Term::cursor_position()};
60 Term::Private::out.write("\u001b%G"); // Try to activate UTF-8 (NOT warranty)
61 const std::string read{Term::Private::in.read()};
62 const Term::Cursor cursor_after{Term::cursor_position()};
63 const std::size_t moved{cursor_after.column() - cursor_before.column()};
64 std::string rem;
65 rem.reserve(moved * 3);
66 for(std::size_t i = 0; i != moved; ++i) { rem += "\b \b"; }
68 enabled = 0 == moved;
69 }
70 else
71 {
72 // Does not return the original charset but, the default defined by standard ISO 8859-1 (ISO 2022);
73 Term::Private::out.write("\u001b%@");
74 }
75#endif
76}
77
79try
80{
81 static bool enabled{false};
82#if defined(_WIN32)
83 static DWORD originalOut{0};
84 static DWORD originalIn{0};
85 if(!enabled)
86 {
87 Term::Private::WindowsError().check_if(GetConsoleMode(Private::out.handle(), &originalOut) == 0).throw_exception("GetConsoleMode(Private::out.handle(), &originalOut)");
88 Term::Private::WindowsError().check_if(GetConsoleMode(Private::in.handle(), &originalIn) == 0).throw_exception("GetConsoleMode(Private::in.handle(), &originalIn)");
89 DWORD in{static_cast<DWORD>((originalIn & ~(ENABLE_QUICK_EDIT_MODE | setFocusEvents() | setMouseEvents())) | (ENABLE_EXTENDED_FLAGS))};
90 DWORD out{originalOut};
91 // Check if ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN can be activated, if not we are a legacy terminal.
92 DWORD test = out;
94 if(!SetConsoleMode(Private::out.handle(), test)) { SetConsoleMode(Private::out.handle(), out); }
95 else
96 {
99 }
100 if(!SetConsoleMode(Private::out.handle(), out)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle()"); }
101 if(!SetConsoleMode(Private::in.handle(), in)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), in)"); }
102 enabled = true;
103 }
104 else
105 {
106 if(!SetConsoleMode(Private::out.handle(), originalOut)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::out.handle(), originalOut)"); }
107 if(!SetConsoleMode(Private::in.handle(), originalIn)) { throw Term::Private::WindowsException(GetLastError(), "SetConsoleMode(Private::in.handle(), originalIn)"); }
108 }
109#else
110 static termios orig_termios;
111 if(!enabled)
112 {
115 if(!Private::out.null()) { Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &orig_termios) == -1).throw_exception("tcgetattr() failed"); }
116 enabled = true;
117 }
118 else
119 {
121 unsetMouseEvents();
122 unsetFocusEvents();
123 if(!Private::out.null()) { Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &orig_termios) == -1).throw_exception("tcsetattr() failed in destructor"); }
124 }
125#endif
126}
127catch(...)
128{
130}
131
133{
134#if defined(_WIN32)
135 return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
136#else
137 return Term::Private::out.write("\u001b[?1002h\u001b[?1003h\u001b[?1006h");
138#endif
139}
140
142{
143#if defined(_WIN32)
144 return static_cast<std::size_t>(ENABLE_MOUSE_INPUT);
145#else
146 return Term::Private::out.write("\u001b[?1006l\u001b[?1003l\u001b[?1002l");
147#endif
148}
149
151{
152#if defined(_WIN32)
153 return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
154#else
155 return Term::Private::out.write("\u001b[?1004h");
156#endif
157}
158
160{
161#if defined(_WIN32)
162 return static_cast<std::size_t>(ENABLE_WINDOW_INPUT);
163#else
164 return Term::Private::out.write("\u001b[?1004l");
165#endif
166}
167
169{
170 static bool activated{false};
171#if defined(_WIN32)
172 static DWORD flags{0};
173 if(!activated)
174 {
175 if(!Private::out.null())
176 if(!GetConsoleMode(Private::in.handle(), &flags)) { throw Term::Private::WindowsException(GetLastError()); }
177 activated = true;
178 return;
179 }
180 DWORD send = flags;
181 if(m_options.has(Option::Raw))
182 {
183 send &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
184 send |= (setFocusEvents() | setMouseEvents());
185 }
186 else if(m_options.has(Option::Cooked))
187 {
188 send |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
189 send &= ~(setFocusEvents() | setMouseEvents());
190 }
191 if(m_options.has(Option::NoSignalKeys)) { send &= ~ENABLE_PROCESSED_INPUT; }
192 else if(m_options.has(Option::SignalKeys)) { send |= ENABLE_PROCESSED_INPUT; }
193 if(!Private::out.null())
194 if(!SetConsoleMode(Private::in.handle(), send)) { throw Term::Private::WindowsException(GetLastError()); }
195#else
196 if(!Private::out.null())
197 {
198 static ::termios raw = {};
199 if(!activated)
200 {
201 Term::Private::Errno().check_if(tcgetattr(Private::out.fd(), &raw) == -1).throw_exception("tcgetattr(Private::out.fd(), &raw)");
202 raw.c_cflag &= ~static_cast<std::size_t>(CSIZE | PARENB);
203 raw.c_cflag |= CS8;
204 raw.c_cc[VMIN] = 1;
205 raw.c_cc[VTIME] = 0;
206 activated = true;
207 return;
208 }
209 ::termios send = raw;
210 if(m_options.has(Option::Raw))
211 {
212 send.c_iflag &= ~static_cast<std::size_t>(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | INPCK);
213 // This disables output post-processing, requiring explicit \r\n. We
214 // keep it enabled, so that in C++, one can still just use std::endl
215 // for EOL instead of "\r\n".
216 //send.c_oflag &= ~static_cast<std::size_t>(OPOST);
217 send.c_lflag &= ~static_cast<std::size_t>(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
218 setMouseEvents();
219 setFocusEvents();
220 }
221 else if(m_options.has(Option::Cooked))
222 {
223 send = raw;
224 unsetMouseEvents();
225 unsetFocusEvents();
226 }
227 if(m_options.has(Option::NoSignalKeys)) { send.c_lflag &= ~static_cast<std::size_t>(ISIG); } //FIXME need others flags !
228 if(m_options.has(Option::SignalKeys)) { send.c_lflag |= ISIG; }
229 Term::Private::Errno().check_if(tcsetattr(Private::out.fd(), TCSAFLUSH, &send) == -1).throw_exception("tcsetattr(Private::out.fd(), TCSAFLUSH, &send)");
230 }
231#endif
232}
std::size_t column() const
Definition cursor.cpp:16
void throw_exception(const std::string &str={}) const
Errno & check_if(const bool &ret) noexcept
std::string read() const
Definition file.cpp:127
std::size_t write(const std::string &str) const
Definition file.cpp:101
static void registerSigwinch()
Definition sigwinch.cpp:49
static void unblockSigwinch()
Definition sigwinch.cpp:75
static void blockSigwinch()
Definition sigwinch.cpp:65
WindowsError & check_if(const bool &ret) noexcept
Definition exception.cpp:75
void throw_exception(const std::string &str=std::string()) const
Definition exception.cpp:82
void setMode() const
Set mode raw/cooked.
static void set_unset_utf8()
static void store_and_restore() noexcept
Store and restore the default state of the terminal.
static std::size_t setFocusEvents()
static std::size_t setMouseEvents()
static std::size_t unsetMouseEvents()
static std::size_t unsetFocusEvents()
InputFileHandler & in
Definition file.cpp:43
OutputFileHandler & out
Definition file.cpp:44
@ Cooked
Set terminal in cooked mode.
@ Raw
Set terminal in raw mode.
@ NoSignalKeys
Disable the signal keys (Ctrl+C, etc...) will not be processed by the OS and will appears has standar...
@ SignalKeys
Enable the signal keys (Ctrl+C, etc...), if activated these keys will have their default OS behaviour...
Term::Cursor cursor_position()
Definition cursor.cpp:26
#define DISABLE_NEWLINE_AUTO_RETURN
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_INPUT