cpp-terminal 1.0.0
Small C++ library for writing multiplatform terminal applications
Loading...
Searching...
No Matches
input.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-2024 cpp-terminal
6*
7* SPDX-License-Identifier: MIT
8*/
9#if defined(_WIN32)
11
12 #include <vector>
13 #pragma warning(push)
14 #pragma warning(disable : 4668)
15 #define WIN32_LEAN_AND_MEAN
16 #include <windows.h>
17 #pragma warning(pop)
18#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
19 #include <cerrno>
20 #include <csignal>
21 #include <sys/ioctl.h>
22 #include <thread>
23 #include <unistd.h>
24#else
25 #include <memory>
26 #include <sys/epoll.h>
27#endif
28
36
37#include <mutex>
38#include <string>
39
40#if defined(_WIN32)
41Term::Button::Action getAction(const std::int32_t& old_state, const std::int32_t& state, const std::int32_t& type)
42{
43 if(((state - old_state) >> (type - 1)) == 1) return Term::Button::Action::Pressed;
44 else if(((old_state - state) >> (type - 1)) == 1)
46 else
48}
49Term::Button setButton(const std::int32_t& old_state, const std::int32_t& state)
50{
52 action = getAction(old_state, state, FROM_LEFT_1ST_BUTTON_PRESSED);
54
55 action = getAction(old_state, state, FROM_LEFT_2ND_BUTTON_PRESSED);
57
58 action = getAction(old_state, state, FROM_LEFT_3RD_BUTTON_PRESSED);
60
61 action = getAction(old_state, state, FROM_LEFT_4TH_BUTTON_PRESSED);
63
64 action = getAction(old_state, state, RIGHTMOST_BUTTON_PRESSED);
66
68}
69
70void sendString(Term::Private::BlockingQueue& events, std::wstring& str)
71{
72 if(!str.empty())
73 {
74 events.push(Term::Event(Term::Private::to_narrow(str.c_str())));
75 str.clear();
76 }
77}
78
79#endif
81{
82 if(m_thread.joinable()) m_thread.join();
83}
84
86
88
90
92{
93#if defined(__linux__)
94 m_poll = {::epoll_create1(EPOLL_CLOEXEC)};
95 ::epoll_event signal;
96 signal.events = {EPOLLIN};
97 signal.data.fd = {Term::Private::Sigwinch::get()};
98 ::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::Sigwinch::get(), &signal);
99 ::epoll_event input;
100 input.events = {EPOLLIN};
101 input.data.fd = {Term::Private::in.fd()};
102 ::epoll_ctl(m_poll, EPOLL_CTL_ADD, Term::Private::in.fd(), &input);
103#endif
104 if(m_thread.joinable()) m_thread.join();
105 std::thread thread(Term::Private::Input::read_event);
106 m_thread.swap(thread);
107}
108
110{
111 while(true)
112 {
113#if defined(_WIN32)
114 WaitForSingleObject(Term::Private::in.handle(), INFINITE);
115 read_raw();
116#elif defined(__APPLE__) || defined(__wasm__) || defined(__wasm) || defined(__EMSCRIPTEN__)
118 read_raw();
119#else
120 ::epoll_event ret;
121 if(epoll_wait(m_poll, &ret, 1, -1) == 1)
122 {
123 if(Term::Private::Sigwinch::isSigwinch(ret.data.fd)) m_events.push(Term::Screen(screen_size()));
124 else
125 read_raw();
126 }
127#endif
128 }
129}
130
131#if defined(_WIN32)
132void Term::Private::Input::read_windows_key(const std::uint16_t& virtual_key_code, const std::uint32_t& control_key_state, const std::size_t& occurrence)
133{
134 // First check if we have ALT etc (CTRL is already done so skip it)
136 if(((control_key_state & LEFT_ALT_PRESSED) == LEFT_ALT_PRESSED) || ((control_key_state & RIGHT_ALT_PRESSED) == RIGHT_ALT_PRESSED)) toAdd += Term::MetaKey::Value::Alt;
137 if(((control_key_state & LEFT_CTRL_PRESSED) == LEFT_CTRL_PRESSED) || ((control_key_state & RIGHT_CTRL_PRESSED) == RIGHT_CTRL_PRESSED)) toAdd += Term::MetaKey::Value::Ctrl;
138
139 switch(virtual_key_code)
140 {
141 case VK_CANCEL: //??
142 case VK_CLEAR: //??
143 case VK_SHIFT:
144 case VK_CONTROL:
145 case VK_MENU:
146 case VK_PAUSE: //??
147 case VK_CAPITAL:
148 case VK_KANA: //??
149 //case VK_HANGUL: // Same
150 case VK_JUNJA: // ?
151 case VK_FINAL: // ?
152 case VK_HANJA:
153 //case VK_KANJI: // Same
154 case VK_CONVERT: // ?
155 case VK_NONCONVERT: // ?
156 case VK_ACCEPT: // ?
157 case VK_MODECHANGE: // ?
158 break;
159 case VK_PRIOR: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageUp)), occurrence); break;
160 case VK_NEXT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PageDown)), occurrence); break;
161 case VK_END: m_events.push(std::move(toAdd + Term::Key(Key::Value::End)), occurrence); break;
162 case VK_HOME: m_events.push(std::move(toAdd + Term::Key(Key::Value::Home)), occurrence); break;
163 case VK_LEFT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowLeft)), occurrence); break;
164 case VK_UP: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowUp)), occurrence); break;
165 case VK_RIGHT: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowRight)), occurrence); break;
166 case VK_DOWN: m_events.push(std::move(toAdd + Term::Key(Key::Value::ArrowDown)), occurrence); break;
167 case VK_SELECT: //?
168 case VK_PRINT: //?
169 case VK_EXECUTE: //?
170 break;
171 case VK_SNAPSHOT: m_events.push(std::move(toAdd + Term::Key(Key::Value::PrintScreen)), occurrence); break;
172 case VK_INSERT: m_events.push(std::move(toAdd + Term::Key(Key::Value::Insert)), occurrence); break;
173 case VK_DELETE: m_events.push(std::move(toAdd + Term::Key(Key::Value::Del)), occurrence); break;
174 case VK_HELP: //?
175 case VK_LWIN: //Maybe allow to detect Windows key Left and right
176 case VK_RWIN: //Maybe allow to detect Windows key Left and right
177 case VK_APPS: //?
178 case VK_SLEEP: //?
179 break;
180 case VK_F1: m_events.push(std::move(toAdd + Term::Key(Key::Value::F1)), occurrence); break;
181 case VK_F2: m_events.push(std::move(toAdd + Term::Key(Key::Value::F2)), occurrence); break;
182 case VK_F3: m_events.push(std::move(toAdd + Term::Key(Key::Value::F3)), occurrence); break;
183 case VK_F4: m_events.push(std::move(toAdd + Term::Key(Key::Value::F4)), occurrence); break;
184 case VK_F5: m_events.push(std::move(toAdd + Term::Key(Key::Value::F5)), occurrence); break;
185 case VK_F6: m_events.push(std::move(toAdd + Term::Key(Key::Value::F6)), occurrence); break;
186 case VK_F7: m_events.push(std::move(toAdd + Term::Key(Key::Value::F7)), occurrence); break;
187 case VK_F8: m_events.push(std::move(toAdd + Term::Key(Key::Value::F8)), occurrence); break;
188 case VK_F9: m_events.push(std::move(toAdd + Term::Key(Key::Value::F9)), occurrence); break;
189 case VK_F10: m_events.push(std::move(toAdd + Term::Key(Key::Value::F10)), occurrence); break;
190 case VK_F11: m_events.push(std::move(toAdd + Term::Key(Key::Value::F11)), occurrence); break;
191 case VK_F12: m_events.push(std::move(toAdd + Term::Key(Key::Value::F12)), occurrence); break;
192 case VK_F13: m_events.push(std::move(toAdd + Term::Key(Key::Value::F13)), occurrence); break;
193 case VK_F14: m_events.push(std::move(toAdd + Term::Key(Key::Value::F14)), occurrence); break;
194 case VK_F15: m_events.push(std::move(toAdd + Term::Key(Key::Value::F15)), occurrence); break;
195 case VK_F16: m_events.push(std::move(toAdd + Term::Key(Key::Value::F16)), occurrence); break;
196 case VK_F17: m_events.push(std::move(toAdd + Term::Key(Key::Value::F17)), occurrence); break;
197 case VK_F18: m_events.push(std::move(toAdd + Term::Key(Key::Value::F18)), occurrence); break;
198 case VK_F19: m_events.push(std::move(toAdd + Term::Key(Key::Value::F19)), occurrence); break;
199 case VK_F20: m_events.push(std::move(toAdd + Term::Key(Key::Value::F20)), occurrence); break;
200 case VK_F21: m_events.push(std::move(toAdd + Term::Key(Key::Value::F21)), occurrence); break;
201 case VK_F22: m_events.push(std::move(toAdd + Term::Key(Key::Value::F22)), occurrence); break;
202 case VK_F23: m_events.push(std::move(toAdd + Term::Key(Key::Value::F23)), occurrence); break;
203 case VK_F24: m_events.push(std::move(toAdd + Term::Key(Key::Value::F24)), occurrence); break;
204 case VK_NUMLOCK:
205 case VK_SCROLL:
206 default: break;
207 }
208}
209#endif
210
212{
213#ifdef _WIN32
214 DWORD to_read{0};
215 GetNumberOfConsoleInputEvents(Private::in.handle(), &to_read);
216 if(to_read == 0) return;
217 DWORD read{0};
218 std::vector<INPUT_RECORD> events{to_read};
219 if(!ReadConsoleInputW(Private::in.handle(), &events[0], to_read, &read) || read != to_read) Term::Exception("ReadFile() failed");
220 std::wstring ret;
221 bool need_windows_size{false};
222 for(std::size_t i = 0; i != read; ++i)
223 {
224 switch(events[i].EventType)
225 {
226 case KEY_EVENT:
227 {
228 if(events[i].Event.KeyEvent.bKeyDown)
229 {
230 if(events[i].Event.KeyEvent.uChar.UnicodeChar == 0) read_windows_key(events[i].Event.KeyEvent.wVirtualKeyCode, events[i].Event.KeyEvent.dwControlKeyState, events[i].Event.KeyEvent.wRepeatCount);
231 else
232 ret.append(events[i].Event.KeyEvent.wRepeatCount, events[i].Event.KeyEvent.uChar.UnicodeChar == Term::Key::Del ? static_cast<wchar_t>(Key(Term::Key::Value::Backspace)) : static_cast<wchar_t>(events[i].Event.KeyEvent.uChar.UnicodeChar));
233 }
234 break;
235 }
236 case FOCUS_EVENT:
237 {
238 sendString(m_events, ret);
239 m_events.push(Event(Focus(static_cast<Term::Focus::Type>(events[i].Event.FocusEvent.bSetFocus))));
240 break;
241 }
242 case MENU_EVENT:
243 {
244 sendString(m_events, ret);
245 break;
246 }
247 case MOUSE_EVENT:
248 {
249 sendString(m_events, ret);
250 static MOUSE_EVENT_RECORD old_state;
251 if(events[i].Event.MouseEvent.dwEventFlags == MOUSE_WHEELED || events[i].Event.MouseEvent.dwEventFlags == MOUSE_HWHEELED)
252 ;
253 else if(old_state.dwButtonState == events[i].Event.MouseEvent.dwButtonState && old_state.dwMousePosition.X == events[i].Event.MouseEvent.dwMousePosition.X && old_state.dwMousePosition.Y == events[i].Event.MouseEvent.dwMousePosition.Y && old_state.dwEventFlags == events[i].Event.MouseEvent.dwEventFlags)
254 break;
255 std::int32_t state{static_cast<std::int32_t>(events[i].Event.MouseEvent.dwButtonState)};
256 switch(events[i].Event.MouseEvent.dwEventFlags)
257 {
258 case 0:
259 {
260 m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
261 ;
262 break;
263 }
264 case MOUSE_MOVED:
265 {
266 m_events.push(Term::Mouse(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
267 ;
268 break;
269 }
270 case DOUBLE_CLICK:
271 {
272 m_events.push(Term::Mouse(Term::Button(setButton(static_cast<std::int32_t>(old_state.dwButtonState), state).type(), Term::Button::Action::DoubleClicked), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
273 break;
274 }
275 case MOUSE_WHEELED:
276 {
277 if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledUp), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
278 else
279 m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::RolledDown), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
280 break;
281 }
282 case MOUSE_HWHEELED:
283 {
284 if(state > 0) m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToRight), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
285 else
286 m_events.push(Term::Mouse(Button(Term::Button::Type::Wheel, Term::Button::Action::ToLeft), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.Y), static_cast<std::uint16_t>(events[i].Event.MouseEvent.dwMousePosition.X)));
287 break;
288 }
289 default: break;
290 }
291 old_state = events[i].Event.MouseEvent;
292 break;
293 }
294 case WINDOW_BUFFER_SIZE_EVENT:
295 {
296 need_windows_size = true; // if we send directly it's too much generations
297 sendString(m_events, ret);
298 break;
299 }
300 default: break;
301 }
302 }
303 sendString(m_events, ret);
304 if(need_windows_size == true) { m_events.push(screen_size()); }
305#else
307 std::string ret = Term::Private::in.read();
309 if(!ret.empty()) m_events.push(Event(ret.c_str()));
310#endif
311}
312
314
316{
317 static bool activated{false};
318 if(!activated)
319 {
320 init_thread();
321 m_thread.detach();
322 activated = true;
323 }
324}
325
326Term::Event Term::Private::Input::getEvent() { return m_events.pop(); }
327
329{
330 static std::mutex cv_m;
331 static std::unique_lock<std::mutex> lk(cv_m);
332 if(m_events.empty()) m_events.wait_for_events(lk);
333 return m_events.pop();
334}
335
336static Term::Private::Input m_input;
337
339{
340 m_input.startReading();
341 return m_input.getEventBlocking();
342}
Term::Button::Type type() const noexcept
Definition mouse.cpp:14
Class to return the focus of the terminal.
Definition focus.hpp:22
@ ArrowRight
Definition key.hpp:239
@ ArrowLeft
Definition key.hpp:238
@ PageDown
Definition key.hpp:247
@ PageUp
Definition key.hpp:246
@ PrintScreen
Definition key.hpp:272
@ ArrowUp
Definition key.hpp:240
@ Backspace
Definition key.hpp:229
@ ArrowDown
Definition key.hpp:241
@ Insert
Definition key.hpp:244
void push(const Term::Event &value, const std::size_t &occurrence=1)
std::int32_t fd() const noexcept
Definition file.cpp:97
std::string read() const
Definition file.cpp:127
static int m_poll
Definition input.hpp:89
static void init_thread()
Definition input.cpp:91
static std::thread m_thread
Definition input.hpp:85
static void read_raw()
Definition input.cpp:211
static Term::Event getEventBlocking()
Definition input.cpp:328
static Term::Event getEvent()
Definition input.cpp:326
static void startReading()
Definition input.cpp:315
static void read_windows_key(const std::uint16_t &virtual_key_code, const std::uint32_t &control_key_state, const std::size_t &occurrence)
Definition input.cpp:132
static Term::Private::BlockingQueue m_events
Definition input.hpp:42
static void read_event()
Definition input.cpp:109
static std::int32_t get() noexcept
Definition sigwinch.cpp:38
static bool isSigwinch(const std::int32_t &file_descriptor=-1) noexcept
Definition sigwinch.cpp:85
Term::Button::Action getAction(const std::int32_t &old_state, const std::int32_t &state, const std::int32_t &type)
Definition input.cpp:41
Term::Button setButton(const std::int32_t &old_state, const std::int32_t &state)
Definition input.cpp:49
void sendString(Term::Private::BlockingQueue &events, std::wstring &str)
Definition input.cpp:70
std::string to_narrow(const std::wstring &wstr)
Definition unicode.cpp:26
InputFileHandler & in
Definition file.cpp:43
Term::Event read_event()
Definition input.cpp:338
Screen screen_size()
Definition screen.cpp:24