use crate::page::Report;
use gpui::{App, KeyContext, Keymap, Keystroke, Modifiers, Window};
use std::{cell::RefCell, rc::Rc};
use webview2_com::{
AcceleratorKeyPressedEventHandler,
Microsoft::Web::WebView2::Win32::{
COREWEBVIEW2_KEY_EVENT_KIND, COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN,
COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN, ICoreWebView2Controller,
},
};
use windows::Win32::UI::Input::KeyboardAndMouse::*;
pub(super) struct Keys {
keymap: Rc<RefCell<Keymap>>,
contexts: RefCell<Vec<KeyContext>>,
}
impl Keys {
pub(super) fn new(cx: &App) -> Self {
Self {
keymap: cx.key_bindings(),
contexts: RefCell::default(),
}
}
pub(super) fn watch(&self, window: &Window) {
*self.contexts.borrow_mut() = window.context_stack();
}
fn bound(&self, keystroke: &Keystroke) -> bool {
let Ok(keymap) = self.keymap.try_borrow() else {
return false;
};
let (bindings, pending) =
keymap.bindings_for_input(std::slice::from_ref(keystroke), &self.contexts.borrow());
pending || !bindings.is_empty()
}
}
pub(super) fn attach(
controller: &ICoreWebView2Controller,
keys: Rc<Keys>,
reports: async_channel::Sender<Report>,
) {
let mut token = 0;
unsafe {
let _ = controller.add_AcceleratorKeyPressed(
&AcceleratorKeyPressedEventHandler::create(Box::new(move |_, args| {
let Some(args) = args else { return Ok(()) };
let mut kind = COREWEBVIEW2_KEY_EVENT_KIND::default();
args.KeyEventKind(&mut kind)?;
if kind != COREWEBVIEW2_KEY_EVENT_KIND_KEY_DOWN
&& kind != COREWEBVIEW2_KEY_EVENT_KIND_SYSTEM_KEY_DOWN
{
return Ok(());
}
let mut vkey = 0;
args.VirtualKey(&mut vkey)?;
let Some(keystroke) = keystroke(VIRTUAL_KEY(vkey as u16)) else {
return Ok(());
};
if keys.bound(&keystroke) {
args.SetHandled(true)?;
let _ = reports.try_send(Report::Key(keystroke));
}
Ok(())
})),
&mut token,
);
}
}
fn keystroke(vkey: VIRTUAL_KEY) -> Option<Keystroke> {
let mut modifiers = Modifiers {
control: held(VK_CONTROL),
alt: held(VK_MENU),
shift: held(VK_SHIFT),
platform: held(VK_LWIN) || held(VK_RWIN),
function: false,
};
let function = (VK_F1.0..=VK_F24.0).contains(&vkey.0);
if !(modifiers.control || modifiers.alt || function) {
return None;
}
let key = match named(vkey) {
Some(key) => key.to_owned(),
None if modifiers.shift && shifts(vkey) => {
modifiers.shift = false;
shifted(vkey)?
}
None => {
let char = unsafe { MapVirtualKeyW(u32::from(vkey.0), MAPVK_VK_TO_CHAR) };
char::from_u32(char & 0xFFFF)
.filter(|char| *char != '\0')?
.to_ascii_lowercase()
.to_string()
}
};
Some(Keystroke {
modifiers,
key,
key_char: None,
})
}
fn held(vkey: VIRTUAL_KEY) -> bool {
unsafe { GetKeyState(i32::from(vkey.0)) < 0 }
}
fn named(vkey: VIRTUAL_KEY) -> Option<&'static str> {
const FUNCTION: [&str; 24] = [
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14",
"f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24",
];
Some(match vkey {
VK_SPACE => "space",
VK_BACK => "backspace",
VK_RETURN => "enter",
VK_TAB => "tab",
VK_UP => "up",
VK_DOWN => "down",
VK_RIGHT => "right",
VK_LEFT => "left",
VK_HOME => "home",
VK_END => "end",
VK_PRIOR => "pageup",
VK_NEXT => "pagedown",
VK_ESCAPE => "escape",
VK_INSERT => "insert",
VK_DELETE => "delete",
VK_APPS => "menu",
vkey if (VK_F1.0..=VK_F24.0).contains(&vkey.0) => FUNCTION[usize::from(vkey.0 - VK_F1.0)],
_ => return None,
})
}
fn shifts(vkey: VIRTUAL_KEY) -> bool {
(VK_0.0..=VK_9.0).contains(&vkey.0)
|| matches!(
vkey,
VK_OEM_1
| VK_OEM_2
| VK_OEM_3
| VK_OEM_4
| VK_OEM_5
| VK_OEM_6
| VK_OEM_7
| VK_OEM_8
| VK_OEM_102
| VK_OEM_PLUS
| VK_OEM_MINUS
| VK_OEM_COMMA
| VK_OEM_PERIOD
| VK_ABNT_C1
)
}
fn shifted(vkey: VIRTUAL_KEY) -> Option<String> {
let mut state = [0u8; 256];
state[usize::from(VK_SHIFT.0)] = 0x80;
let mut buffer = [0u16; 8];
let written = unsafe {
let scan = MapVirtualKeyW(u32::from(vkey.0), MAPVK_VK_TO_VSC);
ToUnicode(u32::from(vkey.0), scan, Some(&state), &mut buffer, 0x4)
};
let written = usize::try_from(written).ok().filter(|n| *n > 0)?;
String::from_utf16(&buffer[..written]).ok()
}