1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
module;
#include <algorithm>
#include <termios.h>
#include <unistd.h>
#include <cerrno>
#include <system_error>
#include <utility>
export module ttwhy.terminal:scoped_attributes;
namespace ttwhy::terminal
{
auto static read_attributes(int file_descriptor) -> termios
{
auto active_attributes = termios{};
if (::tcgetattr(file_descriptor, &active_attributes))
{
throw std::system_error(errno, std::system_category(), "failed to read termios attributes");
}
return active_attributes;
}
auto static get_line_discipline_flag(int file_descriptor, int flag) -> bool
{
auto active_attributes = read_attributes(file_descriptor);
return active_attributes.c_lflag & flag;
}
auto static set_line_discipline_flag(int file_descriptor, int flag, bool enabled) -> void
{
auto active_attributes = read_attributes(file_descriptor);
if ((active_attributes.c_lflag & flag) == enabled)
{
return;
}
if (enabled)
{
active_attributes.c_lflag = active_attributes.c_lflag | flag;
}
else
{
active_attributes.c_lflag = active_attributes.c_lflag & ~flag;
}
if (::tcsetattr(file_descriptor, TCSANOW, &active_attributes))
{
throw std::system_error(errno, std::system_category(), "failed to write termios attributes");
}
}
//! RAII wrapper storing the current terminal attributes and restoring them on destruction.
export struct scoped_attributes
{
//! Construct and capture the current attributes of a terminal file descriptor.
//!
//! @param file_descriptor The file descriptor of the terminal.
explicit scoped_attributes(int file_descriptor)
: m_file_descriptor{-1}
, m_attributes{}
{
if (isatty(file_descriptor))
{
m_file_descriptor = dup(file_descriptor);
m_attributes = read_attributes(m_file_descriptor);
}
}
scoped_attributes(scoped_attributes const &) = delete;
scoped_attributes(scoped_attributes && other)
: m_file_descriptor{std::exchange(other.m_file_descriptor, -1)}
, m_attributes{std::move(other.m_attributes)}
{}
~scoped_attributes()
{
if (m_file_descriptor > 0)
{
::tcsetattr(m_file_descriptor, TCSANOW, &m_attributes);
::close(m_file_descriptor);
}
}
auto operator=(scoped_attributes const &) -> scoped_attributes & = delete;
auto operator=(scoped_attributes && other) -> scoped_attributes &
{
std::ranges::swap(m_file_descriptor, other.m_file_descriptor);
std::ranges::swap(m_attributes, other.m_attributes);
return *this;
}
//! Check if canonical mode is currently active.
//!
//! @return True if ICANON is set, false otherwise.
[[nodiscard]] auto canonical_mode() const noexcept -> bool
{
return get_line_discipline_flag(m_file_descriptor, ICANON);
}
//! Set whether canonical mode is active or not on an lvalue reference.
//!
//! @param enabled True to enable canonical mode, false to disable.
//! @return Reference to this object for chaining.
auto canonical_mode(bool enabled) & -> scoped_attributes &
{
set_line_discipline_flag(m_file_descriptor, ICANON, enabled);
return *this;
}
//! Set whether canonical mode is active or not on an rvalue.
//!
//! @param enabled True to enable canonical mode, false to disable.
//! @return The modified scoped_attributes object by value.
auto canonical_mode(bool enabled) && -> scoped_attributes
{
set_line_discipline_flag(m_file_descriptor, ICANON, enabled);
return std::move(*this);
}
//! Check if echo is currently active.
//!
//! @return True if ECHO is set, false otherwise.
[[nodiscard]] auto echo() const noexcept -> bool
{
return get_line_discipline_flag(m_file_descriptor, ECHO);
}
//! Set whether echo is active or not on an lvalue reference.
//!
//! @param enabled True to enable echo, false to disable.
//! @return Reference to this object for chaining.
auto echo(bool enabled) & -> scoped_attributes &
{
set_line_discipline_flag(m_file_descriptor, ECHO, enabled);
return *this;
}
//! Set whether echo is active or not on an rvalue.
//!
//! @param enabled True to enable echo, false to disable.
//! @return The modified scoped_attributes object by value.
auto echo(bool enabled) && -> scoped_attributes
{
set_line_discipline_flag(m_file_descriptor, ECHO, enabled);
return std::move(*this);
}
//! Capture the currently active terminal mode and mark the attributes as suspended.
//!
//! Must be called before the terminal mode is changed while the process is being stopped.
auto suspend() & -> scoped_attributes &
{
if (m_file_descriptor > 0)
{
m_suspended = true;
m_suspended_canonical = canonical_mode();
m_suspended_echo = echo();
}
return *this;
}
//! Restore the terminal mode captured before suspension.
auto resume() & -> scoped_attributes &
{
if (m_suspended)
{
canonical_mode(m_suspended_canonical);
echo(m_suspended_echo);
m_suspended = false;
}
return *this;
}
private:
int m_file_descriptor;
termios m_attributes;
bool m_suspended{false};
bool m_suspended_canonical{false};
bool m_suspended_echo{false};
};
} // namespace ttwhy::terminal
|