aboutsummaryrefslogtreecommitdiff
path: root/ttwhy/terminal/scoped_attributes.cppm
blob: 56fbe58bea5a553bcb90179c0e42c9698b360b95 (plain)
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
module;

#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);
    }

  private:
    int m_file_descriptor;
    termios m_attributes;
  };

}  // namespace ttwhy::terminal