1 // This file is part of the utio library, a terminal I/O library.
3 // Copyright (C) 2004 by Mike Sharov <msharov@users.sourceforge.net>
4 // This file is free software, distributed under the MIT License.
10 #include <sys/ioctl.h>
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)
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)
42 //----------------------------------------------------------------------
44 /// Loads the keymap and enters UI mode.
45 void CKeyboard::Open (const CTerminfo
& rti
)
53 void CKeyboard::Close (void)
56 cin
.set_nonblock (false);
59 /// Reads a key from stdin.
60 wchar_t CKeyboard::GetKey (bool bBlock
) const
65 if (m_Keydata
.empty() && bBlock
)
69 } while (!(key
= DecodeKey(is
)) && bBlock
);
70 m_Keydata
.erase (m_Keydata
.begin(), is
.pos());
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());
79 while (os
.remaining()) {
80 ssize_t br
= read (STDIN_FILENO
, os
.ipos(), os
.remaining());
83 else if (br
< 0 && errno
!= EAGAIN
&& errno
!= EINTR
)
84 throw file_exception ("read", "stdin");
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
96 FD_SET (STDIN_FILENO
, &fds
);
97 struct timeval tv
= { 0, timeout
};
98 struct timeval
* ptv
= timeout
? &tv
: NULL
;
103 rv
= select (1, &fds
, NULL
, NULL
, ptv
);
104 } while (errno
== EINTR
);
106 throw file_exception ("select", "stdin");
110 //----------------------------------------------------------------------
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)
122 if (!isatty (STDIN_FILENO
))
123 throw domain_error ("This application only works on a tty.");
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");
150 void CKeyboard::LeaveUIMode (void)
152 if (!s_bTermInUIMode
)
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
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) {
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') {
184 kv
= (isupper(kc
) ? kf_Alt
: kf_Ctrl
) | tolower(kc
);
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...
193 if (kv
== kv_Mouse
&& is
.remaining() >= 3) {
194 static uint8_t s_CurB
= 0;
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)