Fixed the terminal not fully functional error
[utio.git] / kb.cc
blob86359921ef84a1165c8902bfcf0420f99e9ca670
1 // This file is part of the utio library, a terminal I/O library.
2 //
3 // Copyright (C) 2004 by Mike Sharov <msharov@users.sourceforge.net>
4 // This file is free software, distributed under the MIT License.
5 //
6 // kb.cc
7 //
9 #include "kb.h"
10 #include <sys/ioctl.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <errno.h>
17 namespace utio {
19 //----------------------------------------------------------------------
21 // One per process, just like the terminal.
22 bool CKeyboard::s_bTermInUIMode = false;
24 //----------------------------------------------------------------------
26 /// Constructs node with id \p nodeId.
27 CKeyboard::CKeyboard (void)
28 : m_Keymap (),
29 m_Keydata (),
30 m_InitialTermios ()
32 fill_n ((void*) &m_InitialTermios, sizeof(struct termios), '\x0');
33 m_Keydata.reserve (64);
36 /// Destructor cleans up keyboard in case of abnormal termination.
37 CKeyboard::~CKeyboard (void)
39 Close();
42 //----------------------------------------------------------------------
44 /// Loads the keymap and enters UI mode.
45 void CKeyboard::Open (const CTerminfo& rti)
47 LoadKeymap (rti);
48 EnterUIMode();
49 cin.set_nonblock();
52 /// Leaves UI mode.
53 void CKeyboard::Close (void)
55 LeaveUIMode();
56 cin.set_nonblock (false);
59 /// Reads a key from stdin.
60 wchar_t CKeyboard::GetKey (bool bBlock) const
62 wchar_t key = 0;
63 istream is;
64 do {
65 if (m_Keydata.empty() && bBlock)
66 WaitForKeyData();
67 ReadKeyData();
68 is.link (m_Keydata);
69 } while (!(key = DecodeKey(is)) && bBlock);
70 m_Keydata.erase (m_Keydata.begin(), is.pos());
71 return (key);
74 /// Reads all available stdin data (nonblocking)
75 void CKeyboard::ReadKeyData (void) const
77 ostream os (m_Keydata.end(), m_Keydata.capacity() - m_Keydata.size());
78 errno = 0;
79 while (os.remaining()) {
80 ssize_t br = read (STDIN_FILENO, os.ipos(), os.remaining());
81 if (br > 0)
82 os.skip (br);
83 else if (br < 0 && errno != EAGAIN && errno != EINTR)
84 throw file_exception ("read", "stdin");
85 else
86 break;
88 m_Keydata.resize (m_Keydata.size() + os.pos());
91 /// Blocks until something is available on stdin. Returns false on \p timeout.
92 bool CKeyboard::WaitForKeyData (long timeout) const
94 fd_set fds;
95 FD_ZERO (&fds);
96 FD_SET (STDIN_FILENO, &fds);
97 struct timeval tv = { 0, timeout };
98 struct timeval* ptv = timeout ? &tv : NULL;
99 errno = 0;
100 int rv;
101 do {
102 errno = 0;
103 rv = select (1, &fds, NULL, NULL, ptv);
104 } while (errno == EINTR);
105 if (rv < 0)
106 throw file_exception ("select", "stdin");
107 return (rv);
110 //----------------------------------------------------------------------
112 /// Enters UI mode.
114 /// This turns off various command-line stuff, like buffering, echoing,
115 /// scroll lock, shift-pgup/dn, etc., which can be very ugly or annoying
116 /// in a GUI application.
118 void CKeyboard::EnterUIMode (void)
120 if (s_bTermInUIMode)
121 return;
122 if (!isatty (STDIN_FILENO))
123 throw domain_error ("This application only works on a tty.");
125 int flag;
126 if ((flag = fcntl (STDIN_FILENO, F_GETFL)) < 0)
127 throw file_exception ("F_GETFL", "stdin");
128 if (fcntl (STDIN_FILENO, F_SETFL, flag | O_NONBLOCK))
129 throw file_exception ("F_SETFL", "stdin");
131 if (-1 == tcgetattr (STDIN_FILENO, &m_InitialTermios))
132 throw libc_exception ("tcgetattr");
133 struct termios tios (m_InitialTermios);
134 tios.c_lflag &= ~(ICANON | ECHO); // No by-line buffering, no echo.
135 tios.c_iflag &= ~(IXON | IXOFF); // No ^s scroll lock (whose dumb idea was it?)
136 tios.c_cc[VMIN] = 1; // Read at least 1 character on each read().
137 tios.c_cc[VTIME] = 0; // Disable time-based preprocessing (Esc sequences)
138 tios.c_cc[VQUIT] = 0xFF; // Disable ^\. Root window will handle.
139 tios.c_cc[VSUSP] = 0xFF; // Disable ^z. Suspends in UI mode result in garbage.
141 if (-1 == tcflush (STDIN_FILENO, TCIFLUSH)) // Flush the input queue; who knows what was pressed.
142 throw libc_exception ("tcflush");
144 s_bTermInUIMode = true; // Cleanup is needed after the next statement.
145 if (-1 == tcsetattr (STDIN_FILENO, TCSAFLUSH, &tios))
146 throw libc_exception ("tcsetattr");
149 /// Leaves UI mode.
150 void CKeyboard::LeaveUIMode (void)
152 if (!s_bTermInUIMode)
153 return;
154 tcflush (STDIN_FILENO, TCIFLUSH); // Should not leave any garbage for the shell
155 if (tcsetattr (STDIN_FILENO, TCSANOW, &m_InitialTermios))
156 throw file_exception ("tcsetattr", "stdin");
157 s_bTermInUIMode = false;
160 //----------------------------------------------------------------------
162 /// Decodes a keystring in \p str that was read from stdin into an eventcode.
163 wchar_t CKeyboard::DecodeKey (istream& is) const
165 wchar_t kv = 0;
166 if (!is.remaining())
167 return (kv);
169 // Find the longest match in the keymap.
170 size_t matchedSize = 0, kss, ki = 0;
171 for (const char* ks = m_Keymap.begin(); ki < kv_nKeys; ++ki, ks += kss + 1) {
172 if ((kss = strlen(ks)) <= is.remaining() && kss > matchedSize && strncmp (is.ipos(), ks, kss) == 0) {
173 kv = ki + kv_First;
174 matchedSize = kss;
177 is.skip (matchedSize);
179 // Read the keystring as UTF-8 if enough bytes are available,
180 if ((!kv || kv == kv_Esc) && is.remaining() && (matchedSize = min (Utf8SequenceBytes(*is.ipos()), is.remaining()))) {
181 char kc = *is.ipos();
182 if (isalpha (kc + 0x60) && kc != '\t' && kc != '\n') {
183 kc += 0x60;
184 kv = (isupper(kc) ? kf_Alt : kf_Ctrl) | tolower(kc);
185 } else
186 kv = (((kv!=kv_Esc)-1) & kf_Alt) | *utf8in(is.ipos());
187 is.skip (matchedSize);
189 if (kv == 0x7F) // xterms are not always consistent with this...
190 kv = kv_Backspace;
192 // Decode mouse data
193 if (kv == kv_Mouse && is.remaining() >= 3) {
194 static uint8_t s_CurB = 0;
195 uint8_t x, y, b, bs;
196 b = is.ipos()[0]; x = is.ipos()[1]; y = is.ipos()[2];
197 x -= '!'; y -= '!'; b = (b + 1) & 3; // Coordinates are '!'-based, buttons are 0,1,2 with motion and drag flags, which I mask off.
198 bs = b - s_CurB; bs = (bs != 0) + (int8_t(bs) < 0); // Convert to "down" or "up" values.
199 kv = kf_Mouse | (bs << 18) | (max(b,s_CurB) << 16) | (x << 8) | y; // Packing should match UMouseEvent structure (doing it manually here generates considerably less code)
200 s_CurB = b;
201 is.skip (3);
203 return (kv);
206 } // namespace utio