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