use super::{check, ffi, Emission, Vt};
use crate::model::MAX_INPUT_BYTES;
use crate::Error;
use std::ffi::CStr;
use strop_core::frontend_input::{Input, KeyCode, KeyEvent, KeyKind, MediaKey, ModifierKey};
impl Vt {
pub fn keyboard_capabilities(&mut self, flags: u8) -> Result<(), Error> {
check("keyboard capabilities", unsafe {
ffi::strop_vt_keyboard(self.handle.as_ptr(), flags)
})
}
pub fn focus(&mut self, focused: bool) -> Result<Emission, Error> {
let mut bytes = [0; 16];
let mut written = 0;
check("focus", unsafe {
ffi::strop_vt_focus(
self.handle.as_ptr(),
i32::from(focused),
bytes.as_mut_ptr(),
bytes.len(),
&mut written,
)
})?;
if written > bytes.len() {
return Err(Error::State("focus encoder exceeded its output bound"));
}
Ok(Emission {
reply: bytes[..written].to_vec(),
effects: Vec::new(),
})
}
pub fn input(&mut self, input: &Input, paste_confirmed: bool) -> Result<Emission, Error> {
match input {
Input::Key(key) => self.key(*key),
Input::Text(text) => {
if text.len() > MAX_INPUT_BYTES {
return Err(Error::Capacity("committed terminal text"));
}
let mut emission = Emission::default();
for ch in text.chars() {
let next = self.key(KeyEvent::press(KeyCode::Char(ch)))?;
if next.reply.len() > MAX_INPUT_BYTES.saturating_sub(emission.reply.len()) {
return Err(Error::Capacity("encoded terminal text"));
}
emission.reply.extend(next.reply);
emission.effects.extend(next.effects);
}
Ok(emission)
}
Input::Paste(text) => {
if text.len() > MAX_INPUT_BYTES - 12 {
return Err(Error::Capacity("terminal paste"));
}
let code = unsafe {
ffi::strop_vt_paste(
self.handle.as_ptr(),
text.as_ptr(),
text.len(),
i32::from(paste_confirmed),
)
};
if code == -7 {
return Err(Error::PasteNeedsConfirmation);
}
check("paste", code)?;
self.take_events()
}
}
}
fn key(&mut self, mut key: KeyEvent) -> Result<Emission, Error> {
if key.modifiers.hyper || key.modifiers.meta {
return Err(Error::Unavailable(
"the selected key encoder cannot represent Hyper/Meta modifier state".into(),
));
}
if matches!(key.code, KeyCode::Null | KeyCode::Char('\0')) {
key.code = KeyCode::Char(' ');
key.modifiers.control = true;
}
let mut function = [0u8; 5];
let (name, scalar) = match key.code {
KeyCode::Char(ch) => (c"", u32::from(ch)),
KeyCode::Function(number) if (1..=25).contains(&number) => {
function[0] = b'F';
let length = if number < 10 {
function[1] = b'0' + number;
2
} else {
function[1] = b'0' + number / 10;
function[2] = b'0' + number % 10;
3
};
let name = CStr::from_bytes_with_nul(&function[..=length])
.map_err(|_| Error::Protocol("invalid function-key encoding".into()))?;
(name, 0)
}
KeyCode::Function(_) => {
return Err(Error::Unavailable(
"function key is outside the native encoder's supported range".into(),
))
}
KeyCode::Escape => (c"Escape", 0),
KeyCode::Enter => (c"Enter", 0),
KeyCode::Backspace => (c"Backspace", 0),
KeyCode::Up => (c"Up", 0),
KeyCode::Down => (c"Down", 0),
KeyCode::Left => (c"Left", 0),
KeyCode::Right => (c"Right", 0),
KeyCode::Home => (c"Home", 0),
KeyCode::End => (c"End", 0),
KeyCode::PageUp => (c"PageUp", 0),
KeyCode::PageDown => (c"PageDown", 0),
KeyCode::Tab => (c"Tab", 0),
KeyCode::BackTab => {
key.modifiers.shift = true;
(c"Tab", 0)
}
KeyCode::Delete => (c"Delete", 0),
KeyCode::Insert => (c"Insert", 0),
KeyCode::CapsLock => (c"CapsLock", 0),
KeyCode::ScrollLock => (c"ScrollLock", 0),
KeyCode::NumLock => (c"NumLock", 0),
KeyCode::PrintScreen => (c"PrintScreen", 0),
KeyCode::Pause => (c"Pause", 0),
KeyCode::Menu => (c"Menu", 0),
KeyCode::KeypadBegin => (c"KeypadBegin", 0),
KeyCode::Media(media) => (media_name(media)?, 0),
KeyCode::Modifier(modifier) => (modifier_name(modifier)?, 0),
KeyCode::Null => return Err(Error::Protocol("unreachable NUL key branch".into())),
};
let modifiers = u32::from(key.modifiers.shift)
| (u32::from(key.modifiers.alt) << 1)
| (u32::from(key.modifiers.control) << 2)
| (u32::from(key.modifiers.super_key) << 3);
let state = u32::from(key.state.keypad)
| (u32::from(key.state.caps_lock) << 1)
| (u32::from(key.state.num_lock) << 2);
let action = match key.kind {
KeyKind::Press => 0,
KeyKind::Repeat => 1,
KeyKind::Release => 2,
};
let mut text = [0u8; 4];
let text = match key.code {
KeyCode::Char(ch) => ch.encode_utf8(&mut text).as_bytes(),
_ => &[],
};
let mut output = [0u8; 128];
let mut written = 0;
let code = unsafe {
ffi::strop_vt_key(
self.handle.as_ptr(),
name.as_ptr(),
scalar,
modifiers,
action,
state,
text.as_ptr().cast(),
text.len(),
output.as_mut_ptr(),
output.len(),
&mut written,
)
};
if code == -906 {
return Err(Error::Unavailable(
"key has no representation in the selected native encoder".into(),
));
}
check("key", code)?;
if written > output.len() {
return Err(Error::Protocol("native key result exceeded buffer".into()));
}
let mut emission = self.take_events()?;
emission.reply.extend_from_slice(&output[..written]);
Ok(emission)
}
}
fn media_name(key: MediaKey) -> Result<&'static CStr, Error> {
match key {
MediaKey::PlayPause => Ok(c"MediaPlayPause"),
MediaKey::Stop => Ok(c"MediaStop"),
MediaKey::TrackNext => Ok(c"MediaTrackNext"),
MediaKey::TrackPrevious => Ok(c"MediaTrackPrevious"),
MediaKey::LowerVolume => Ok(c"LowerVolume"),
MediaKey::RaiseVolume => Ok(c"RaiseVolume"),
MediaKey::MuteVolume => Ok(c"MuteVolume"),
_ => Err(Error::Unavailable(
"media key has no native terminal encoding".into(),
)),
}
}
fn modifier_name(key: ModifierKey) -> Result<&'static CStr, Error> {
match key {
ModifierKey::LeftShift => Ok(c"LeftShift"),
ModifierKey::RightShift => Ok(c"RightShift"),
ModifierKey::LeftControl => Ok(c"LeftControl"),
ModifierKey::RightControl => Ok(c"RightControl"),
ModifierKey::LeftAlt => Ok(c"LeftAlt"),
ModifierKey::RightAlt => Ok(c"RightAlt"),
ModifierKey::LeftSuper => Ok(c"LeftSuper"),
ModifierKey::RightSuper => Ok(c"RightSuper"),
_ => Err(Error::Unavailable(
"modifier key has no native terminal encoding".into(),
)),
}
}