1use arboard::Clipboard;
2use winit::event::{ElementState, KeyEvent, Modifiers};
3use winit::keyboard::{Key, NamedKey};
4
5pub struct InputHandler {
7 pub modifiers: Modifiers,
8 clipboard: Option<Clipboard>,
9}
10
11impl InputHandler {
12 pub fn new() -> Self {
14 let clipboard = Clipboard::new().ok();
15 if clipboard.is_none() {
16 log::warn!("Failed to initialize clipboard support");
17 }
18
19 Self {
20 modifiers: Modifiers::default(),
21 clipboard,
22 }
23 }
24
25 pub fn update_modifiers(&mut self, modifiers: Modifiers) {
27 self.modifiers = modifiers;
28 }
29
30 pub fn handle_key_event(&mut self, event: KeyEvent) -> Option<Vec<u8>> {
32 if event.state != ElementState::Pressed {
33 return None;
34 }
35
36 let ctrl = self.modifiers.state().control_key();
37 let shift = self.modifiers.state().shift_key();
38 let alt = self.modifiers.state().alt_key();
39
40 match event.logical_key {
41 Key::Character(ref s) => {
43 if ctrl {
44 let ch = s.chars().next()?;
46
47 if ch.eq_ignore_ascii_case(&'v') {
49 return self.paste_from_clipboard();
50 }
51
52 if ch.is_ascii_alphabetic() {
53 let byte = (ch.to_ascii_lowercase() as u8) - b'a' + 1;
55 return Some(vec![byte]);
56 }
57 }
58
59 let mut bytes = s.as_bytes().to_vec();
61
62 if alt {
64 bytes.insert(0, 0x1b);
65 }
66
67 Some(bytes)
68 }
69
70 Key::Named(named_key) => {
72 if ctrl && matches!(named_key, NamedKey::Space) {
74 return Some(vec![0x00]);
75 }
76
77 if shift && matches!(named_key, NamedKey::Insert) {
79 return self.paste_from_clipboard();
80 }
81
82 let seq = match named_key {
83 NamedKey::Enter => "\r",
84 NamedKey::Tab => "\t",
85 NamedKey::Space => " ",
86 NamedKey::Backspace => "\x7f",
87 NamedKey::Escape => "\x1b",
88 NamedKey::Insert => "\x1b[2~",
89 NamedKey::Delete => "\x1b[3~",
90
91 NamedKey::ArrowUp => "\x1b[A",
93 NamedKey::ArrowDown => "\x1b[B",
94 NamedKey::ArrowRight => "\x1b[C",
95 NamedKey::ArrowLeft => "\x1b[D",
96
97 NamedKey::Home => "\x1b[H",
99 NamedKey::End => "\x1b[F",
100 NamedKey::PageUp => "\x1b[5~",
101 NamedKey::PageDown => "\x1b[6~",
102
103 NamedKey::F1 => "\x1bOP",
105 NamedKey::F2 => "\x1bOQ",
106 NamedKey::F3 => "\x1bOR",
107 NamedKey::F4 => "\x1bOS",
108 NamedKey::F5 => "\x1b[15~",
109 NamedKey::F6 => "\x1b[17~",
110 NamedKey::F7 => "\x1b[18~",
111 NamedKey::F8 => "\x1b[19~",
112 NamedKey::F9 => "\x1b[20~",
113 NamedKey::F10 => "\x1b[21~",
114 NamedKey::F11 => "\x1b[23~",
115 NamedKey::F12 => "\x1b[24~",
116
117 _ => return None,
118 };
119
120 Some(seq.as_bytes().to_vec())
121 }
122
123 _ => None,
124 }
125 }
126
127 pub fn paste_from_clipboard(&mut self) -> Option<Vec<u8>> {
129 if let Some(ref mut clipboard) = self.clipboard {
130 match clipboard.get_text() {
131 Ok(text) => {
132 log::debug!("Pasting from clipboard: {} chars", text.len());
133 let text = text.replace('\n', "\r");
135 Some(text.as_bytes().to_vec())
136 }
137 Err(e) => {
138 log::error!("Failed to get clipboard text: {}", e);
139 None
140 }
141 }
142 } else {
143 log::warn!("Clipboard not available");
144 None
145 }
146 }
147
148 pub fn copy_to_clipboard(&mut self, text: &str) -> Result<(), String> {
150 if let Some(ref mut clipboard) = self.clipboard {
151 clipboard
152 .set_text(text.to_string())
153 .map_err(|e| format!("Failed to set clipboard text: {}", e))
154 } else {
155 Err("Clipboard not available".to_string())
156 }
157 }
158
159 #[cfg(target_os = "linux")]
161 pub fn copy_to_primary_selection(&mut self, text: &str) -> Result<(), String> {
162 use arboard::SetExtLinux;
163
164 if let Some(ref mut clipboard) = self.clipboard {
165 clipboard
166 .set()
167 .clipboard(arboard::LinuxClipboardKind::Primary)
168 .text(text.to_string())
169 .map_err(|e| format!("Failed to set primary selection: {}", e))?;
170 Ok(())
171 } else {
172 Err("Clipboard not available".to_string())
173 }
174 }
175
176 #[cfg(target_os = "linux")]
178 pub fn paste_from_primary_selection(&mut self) -> Option<Vec<u8>> {
179 use arboard::GetExtLinux;
180
181 if let Some(ref mut clipboard) = self.clipboard {
182 match clipboard
183 .get()
184 .clipboard(arboard::LinuxClipboardKind::Primary)
185 .text()
186 {
187 Ok(text) => {
188 log::debug!("Pasting from primary selection: {} chars", text.len());
189 let text = text.replace('\n', "\r");
191 Some(text.as_bytes().to_vec())
192 }
193 Err(e) => {
194 log::error!("Failed to get primary selection text: {}", e);
195 None
196 }
197 }
198 } else {
199 log::warn!("Clipboard not available");
200 None
201 }
202 }
203
204 #[cfg(not(target_os = "linux"))]
206 pub fn copy_to_primary_selection(&mut self, _text: &str) -> Result<(), String> {
207 Ok(()) }
209
210 #[cfg(not(target_os = "linux"))]
212 pub fn paste_from_primary_selection(&mut self) -> Option<Vec<u8>> {
213 self.paste_from_clipboard()
214 }
215}
216
217impl Default for InputHandler {
218 fn default() -> Self {
219 Self::new()
220 }
221}