1use crate::utils::should_use_pty;
2use crate::{handle_keypress, AppContext, FieldUpdate};
3use crate::{run_script, thread_manager::Runnable};
4use crossterm::event::{
5 poll, read, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind,
6};
7use std::sync::mpsc;
8use std::time::Duration;
9
10use crate::thread_manager::*;
11
12use uuid::Uuid;
13
14pub fn format_key_for_pty(code: KeyCode, modifiers: KeyModifiers) -> String {
17 let format_modified_key = |base_seq: &str, modifiers: KeyModifiers| -> String {
19 let mut mod_code = 1;
20 if modifiers.contains(KeyModifiers::SHIFT) {
21 mod_code += 1;
22 }
23 if modifiers.contains(KeyModifiers::ALT) {
24 mod_code += 2;
25 }
26 if modifiers.contains(KeyModifiers::CONTROL) {
27 mod_code += 4;
28 }
29
30 if mod_code == 1 {
31 base_seq.to_string()
32 } else {
33 format!("\x1b[1;{}{}", mod_code, &base_seq[2..])
35 }
36 };
37
38 match code {
39 KeyCode::Char(c) => {
40 if modifiers.contains(KeyModifiers::CONTROL) {
41 match c {
43 'a'..='z' => {
44 let ctrl_code = (c as u8) - b'a' + 1;
45 format!("{}", ctrl_code as char)
46 }
47 'A'..='Z' => {
48 let ctrl_code = (c as u8) - b'A' + 1;
49 format!("{}", ctrl_code as char)
50 }
51 '@' => "\x00".to_string(), '[' => "\x1b".to_string(), '\\' => "\x1c".to_string(), ']' => "\x1d".to_string(), '^' => "\x1e".to_string(), '_' => "\x1f".to_string(), ' ' => "\x00".to_string(), _ => c.to_string(),
59 }
60 } else if modifiers.contains(KeyModifiers::ALT) {
61 format!("\x1b{}", c)
63 } else {
64 c.to_string()
65 }
66 }
67 KeyCode::Enter => {
68 if modifiers.contains(KeyModifiers::CONTROL) {
69 "\n".to_string() } else {
71 "\r".to_string()
72 }
73 }
74 KeyCode::Tab => {
75 if modifiers.contains(KeyModifiers::SHIFT) {
76 "\x1b[Z".to_string() } else {
78 "\t".to_string()
79 }
80 }
81 KeyCode::Backspace => {
82 if modifiers.contains(KeyModifiers::CONTROL) {
83 "\x08".to_string() } else if modifiers.contains(KeyModifiers::ALT) {
85 "\x1b\x7f".to_string() } else {
87 "\x7f".to_string()
88 }
89 }
90 KeyCode::Delete => format_modified_key("\x1b[3~", modifiers),
91 KeyCode::Insert => format_modified_key("\x1b[2~", modifiers),
92
93 KeyCode::Up => format_modified_key("\x1b[A", modifiers),
95 KeyCode::Down => format_modified_key("\x1b[B", modifiers),
96 KeyCode::Right => format_modified_key("\x1b[C", modifiers),
97 KeyCode::Left => format_modified_key("\x1b[D", modifiers),
98
99 KeyCode::Home => format_modified_key("\x1b[H", modifiers),
101 KeyCode::End => format_modified_key("\x1b[F", modifiers),
102
103 KeyCode::PageUp => format_modified_key("\x1b[5~", modifiers),
105 KeyCode::PageDown => format_modified_key("\x1b[6~", modifiers),
106
107 KeyCode::Esc => "\x1b".to_string(),
108
109 KeyCode::F(n) => {
110 let base_seq = match n {
112 1 => "\x1bOP",
113 2 => "\x1bOQ",
114 3 => "\x1bOR",
115 4 => "\x1bOS",
116 5 => "\x1b[15~",
117 6 => "\x1b[17~",
118 7 => "\x1b[18~",
119 8 => "\x1b[19~",
120 9 => "\x1b[20~",
121 10 => "\x1b[21~",
122 11 => "\x1b[23~",
123 12 => "\x1b[24~",
124 13 => "\x1b[25~",
125 14 => "\x1b[26~",
126 15 => "\x1b[28~",
127 16 => "\x1b[29~",
128 17 => "\x1b[31~",
129 18 => "\x1b[32~",
130 19 => "\x1b[33~",
131 20 => "\x1b[34~",
132 21 => "\x1b[35~",
133 22 => "\x1b[36~",
134 23 => "\x1b[37~",
135 24 => "\x1b[38~",
136 _ => return "".to_string(),
137 };
138
139 if modifiers.is_empty() {
140 base_seq.to_string()
141 } else {
142 format_modified_key(base_seq, modifiers)
143 }
144 }
145
146 KeyCode::CapsLock => "".to_string(), KeyCode::ScrollLock => "".to_string(),
149 KeyCode::NumLock => "".to_string(),
150 KeyCode::PrintScreen => "".to_string(),
151 KeyCode::Pause => "".to_string(),
152 KeyCode::Menu => "\x1b[29~".to_string(),
153
154 _ => "".to_string(),
155 }
156}
157create_runnable!(
158 InputLoop,
159 |_inner: &mut RunnableImpl, _app_context: AppContext, _messages: Vec<Message>| -> bool { true },
160 |inner: &mut RunnableImpl,
161 app_context: AppContext,
162 _messages: Vec<Message>|
163 -> (bool, AppContext) {
164 let mut should_continue = true;
165
166 let active_layout = app_context.app.get_active_layout().unwrap();
167
168 if poll(Duration::from_millis(10)).unwrap() {
169 if let Ok(event) = read() {
170 let key_str = match event {
171 Event::Mouse(MouseEvent {
172 kind,
173 column,
174 row,
175 modifiers: _,
176 }) => {
177 match kind {
178 MouseEventKind::ScrollUp => {
179 inner.send_message(Message::ScrollMuxBoxUp());
180 "ScrollUp".to_string()
181 }
182 MouseEventKind::ScrollDown => {
183 inner.send_message(Message::ScrollMuxBoxDown());
184 "ScrollDown".to_string()
185 }
186 MouseEventKind::ScrollLeft => {
187 inner.send_message(Message::ScrollMuxBoxLeft());
188 "ScrollLeft".to_string()
189 }
190 MouseEventKind::ScrollRight => {
191 inner.send_message(Message::ScrollMuxBoxRight());
192 "ScrollRight".to_string()
193 }
194 MouseEventKind::Down(_button) => {
195 inner.send_message(Message::MouseClick(column, row));
197 inner.send_message(Message::MouseDragStart(column, row));
198 format!("MouseClick({}, {})", column, row)
199 }
200 MouseEventKind::Drag(_button) => {
201 inner.send_message(Message::MouseDrag(column, row));
203 format!("MouseDrag({}, {})", column, row)
204 }
205 MouseEventKind::Up(_button) => {
206 inner.send_message(Message::MouseDragEnd(column, row));
208 format!("MouseDragEnd({}, {})", column, row)
209 }
210 _ => return (true, app_context), }
212 }
213 Event::Key(KeyEvent {
214 code, modifiers, ..
215 }) => {
216 let selected_muxboxes = active_layout.get_selected_muxboxes();
218 let focused_muxbox_has_pty = selected_muxboxes
219 .first()
220 .map(|muxbox| should_use_pty(muxbox))
221 .unwrap_or(false);
222
223 if focused_muxbox_has_pty {
224 let key_str = format_key_for_pty(code, modifiers);
226 if let Some(focused_muxbox) = selected_muxboxes.first() {
227 inner.send_message(Message::PTYInput(
228 focused_muxbox.id.clone(),
229 key_str.clone(),
230 ));
231 return (true, app_context);
232 }
233 }
234
235 match code {
236 KeyCode::Char('q') => {
237 inner.send_message(Message::Exit);
238 should_continue = false; "q".to_string()
240 }
241 KeyCode::Tab => {
242 inner.send_message(Message::NextMuxBox());
243 "Tab".to_string()
244 }
245 KeyCode::BackTab => {
246 inner.send_message(Message::PreviousMuxBox());
247 "BackTab".to_string()
248 }
249 KeyCode::Enter => "Enter".to_string(),
250 KeyCode::Down => {
251 inner.send_message(Message::ScrollMuxBoxDown());
252 "Down".to_string()
253 }
254 KeyCode::Up => {
255 inner.send_message(Message::ScrollMuxBoxUp());
256 "Up".to_string()
257 }
258 KeyCode::Left => {
259 inner.send_message(Message::ScrollMuxBoxLeft());
260 "Left".to_string()
261 }
262 KeyCode::Right => {
263 inner.send_message(Message::ScrollMuxBoxRight());
264 "Right".to_string()
265 }
266 KeyCode::PageUp => {
267 if modifiers.contains(KeyModifiers::SHIFT) {
268 inner.send_message(Message::ScrollMuxBoxPageLeft());
269 "Shift+PageUp".to_string()
270 } else {
271 inner.send_message(Message::ScrollMuxBoxPageUp());
272 "PageUp".to_string()
273 }
274 }
275 KeyCode::PageDown => {
276 if modifiers.contains(KeyModifiers::SHIFT) {
277 inner.send_message(Message::ScrollMuxBoxPageRight());
278 "Shift+PageDown".to_string()
279 } else {
280 inner.send_message(Message::ScrollMuxBoxPageDown());
281 "PageDown".to_string()
282 }
283 }
284 KeyCode::Char(c) => {
285 if modifiers.contains(KeyModifiers::CONTROL) {
286 match c {
287 'c' => {
288 inner.send_message(Message::CopyFocusedMuxBoxContent());
289 "Ctrl+c".to_string()
290 }
291 _ => format!("Ctrl+{}", c),
292 }
293 } else if modifiers.contains(KeyModifiers::SUPER) {
294 match c {
295 'c' => {
296 inner.send_message(Message::CopyFocusedMuxBoxContent());
297 "Cmd+c".to_string()
298 }
299 _ => format!("Cmd+{}", c),
300 }
301 } else if modifiers.contains(KeyModifiers::ALT) {
302 format!("Alt+{}", c)
303 } else {
304 c.to_string()
305 }
306 }
307 KeyCode::Backspace => "Backspace".to_string(),
308 KeyCode::Delete => "Delete".to_string(),
309 KeyCode::Esc => "Esc".to_string(),
310 KeyCode::Home => {
311 if modifiers.contains(KeyModifiers::CONTROL) {
312 inner.send_message(Message::ScrollMuxBoxToTop());
314 "Ctrl+Home".to_string()
315 } else {
316 inner.send_message(Message::ScrollMuxBoxToBeginning());
318 "Home".to_string()
319 }
320 }
321 KeyCode::End => {
322 if modifiers.contains(KeyModifiers::CONTROL) {
323 inner.send_message(Message::ScrollMuxBoxToBottom());
325 "Ctrl+End".to_string()
326 } else {
327 inner.send_message(Message::ScrollMuxBoxToEnd());
329 "End".to_string()
330 }
331 }
332 KeyCode::F(n) => format!("F{}", n),
333 KeyCode::Insert => "Insert".to_string(),
334 _ => return (true, app_context),
335 }
336 }
337 _ => return (true, app_context),
338 };
339
340 if let Some(hot_keys) = &app_context.app.hot_keys {
342 if let Some(choice_id) = hot_keys.get(&key_str) {
343 inner.send_message(Message::ExecuteHotKeyChoice(choice_id.clone()));
344 }
345 }
346
347 if let Some(app_key_mappings) = &app_context.app.on_keypress {
348 if let Some(actions) = handle_keypress(&key_str, app_key_mappings) {
349 let libs = app_context.app.libs.clone();
350 let _result = run_script(libs, &actions);
351 }
352 }
353 if let Some(layout_key_mappings) = &active_layout.on_keypress {
354 if let Some(actions) = handle_keypress(&key_str, layout_key_mappings) {
355 let libs = app_context.app.libs.clone();
356 let _ = run_script(libs, &actions);
357 }
358 }
359
360 inner.send_message(Message::KeyPress(key_str.clone()));
361 }
362 }
363 std::thread::sleep(std::time::Duration::from_millis(
364 app_context.config.frame_delay,
365 ));
366
367 (should_continue, app_context)
368 }
369);