use thiserror::Error;
use wayland_client::protocol::{wl_keyboard, wl_registry, wl_seat};
use winit::keyboard::{NamedKey, PhysicalKey};
use winit::platform::scancode::PhysicalKeyExtScancode;
use xkb::x11::{MIN_MAJOR_XKB_VERSION, MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb;
pub const ALT_STR: &str = "Alt";
pub const LOGO_STR: &str = "Super";
pub const MODIFIERS_ORDER: &str = "csam";
pub fn os_specific_key_name(key: NamedKey) -> Option<&'static str> {
match key {
NamedKey::AltGraph => Some("AltGr"),
NamedKey::ArrowDown => Some("Down"),
NamedKey::ArrowLeft => Some("Left"),
NamedKey::ArrowRight => Some("Right"),
NamedKey::ArrowUp => Some("Up"),
_ => None,
}
}
pub fn try_physical_key_name(physical_key: PhysicalKey) -> Option<String> {
physical_key
.to_scancode()
.map(|sc| scancode_name(sc as u16))
}
thread_local! {
static XKB_KEYMAP: xkb::Keymap =
new_keymap().expect("failed to connect to X11 or Wayland to get keymap");
}
pub fn scancode_name(sc: u16) -> String {
let keysym = XKB_KEYMAP.with(|xkb_keymap| {
xkb::State::new(xkb_keymap).key_get_one_sym(xkb::Keycode::new(sc as u32 + 8))
});
match keysym.raw() {
xkb::keysyms::KEY_KP_Insert => "Numpad0".to_string(),
xkb::keysyms::KEY_KP_End => "Numpad1".to_string(),
xkb::keysyms::KEY_KP_Down => "Numpad2".to_string(),
xkb::keysyms::KEY_KP_Next => "Numpad3".to_string(),
xkb::keysyms::KEY_KP_Left => "Numpad4".to_string(),
xkb::keysyms::KEY_KP_Begin => "Numpad5".to_string(),
xkb::keysyms::KEY_KP_Right => "Numpad6".to_string(),
xkb::keysyms::KEY_KP_Home => "Numpad7".to_string(),
xkb::keysyms::KEY_KP_Up => "Numpad8".to_string(),
xkb::keysyms::KEY_KP_Prior => "Numpad9".to_string(),
xkb::keysyms::KEY_KP_Add => "NumpadAdd".to_string(),
xkb::keysyms::KEY_KP_Decimal => "NumpadComma".to_string(),
xkb::keysyms::KEY_KP_Delete => "NumpadDecimal".to_string(),
xkb::keysyms::KEY_KP_Divide => "NumpadDivide".to_string(),
xkb::keysyms::KEY_KP_Enter => "NumpadEnter".to_string(),
xkb::keysyms::KEY_KP_Equal => "NumpadEqual".to_string(),
xkb::keysyms::KEY_KP_Multiply => "NumpadMultiply".to_string(),
xkb::keysyms::KEY_KP_Subtract => "NumpadSubtract".to_string(),
_ => {
let mut key_name = xkb::keysym_get_name(keysym);
if key_name.len() == 1 {
key_name.make_ascii_uppercase();
}
key_name
}
}
}
fn new_keymap() -> Result<xkb::Keymap, KeymapError> {
let wayland_error = match new_wayland_keymap() {
Ok(keymap) => return Ok(keymap),
Err(e) => e,
};
let x11_error = match new_x11_keymap() {
Ok(keymap) => return Ok(keymap),
Err(e) => e,
};
if let Ok(session_type) = std::env::var("XDG_SESSION_TYPE") {
match session_type.as_str() {
"wayland" if std::env::var("WAYLAND_DISPLAY").is_ok() => Err(wayland_error),
"x11" => Err(x11_error),
_ => Err(x11_error),
}
} else {
Err(x11_error)
}
}
#[derive(Error, Debug)]
pub enum KeymapError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("unable to connect to X server")]
X11Connect,
#[error("wayland dispatch error")]
Wayland(#[from] wayland_client::DispatchError),
#[error("unable to connect to wayland")]
WaylandConnect,
#[error("wl_seat not found in available interfaces")]
MissingWlSeat,
#[error("wl_seat does not have keyboard capability")]
MissingKeyboardCapability,
#[error("failed to create keymap")]
FailedToCreateKeymap,
#[error("unsupported keymap format: {0:?}")]
UnsupportedKeymapFormat(wayland_client::WEnum<wl_keyboard::KeymapFormat>),
}
fn new_x11_keymap() -> Result<xkb::Keymap, KeymapError> {
let (connection, _) = xcb::Connection::connect(None).map_err(|_| KeymapError::X11Connect)?;
let mut major_xkb_version_out = 0;
let mut minor_xkb_version_out = 0;
let mut base_event_out = 0;
let mut base_error_out = 0;
let _ = xkb::x11::setup_xkb_extension(
&connection,
MIN_MAJOR_XKB_VERSION,
MIN_MINOR_XKB_VERSION,
xkb::x11::SetupXkbExtensionFlags::NoFlags,
&mut major_xkb_version_out,
&mut minor_xkb_version_out,
&mut base_event_out,
&mut base_error_out,
);
let device_id = xkb::x11::get_core_keyboard_device_id(&connection);
let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
Ok(xkb::x11::keymap_new_from_device(
&ctx,
&connection,
device_id,
0,
))
}
fn new_wayland_keymap() -> Result<xkb::Keymap, KeymapError> {
let connection =
wayland_client::Connection::connect_to_env().map_err(|_| KeymapError::WaylandConnect)?;
let display = connection.display();
let mut state = State::default();
let mut event_queue = connection.new_event_queue::<State>();
let qhandle = event_queue.handle();
let _registry = display.get_registry(&qhandle, ());
event_queue.roundtrip(&mut state)?; if !state.wl_seat {
return Err(KeymapError::MissingWlSeat);
}
event_queue.roundtrip(&mut state)?; if !state.wl_keyboard {
return Err(KeymapError::MissingKeyboardCapability);
}
event_queue.roundtrip(&mut state)?; state
.keymap
.clone()
.ok_or(state.error.unwrap_or(KeymapError::FailedToCreateKeymap))
}
#[derive(Default)]
struct State {
wl_seat: bool,
wl_keyboard: bool,
keymap: Option<xkb::Keymap>,
error: Option<KeymapError>,
}
impl wayland_client::Dispatch<wl_registry::WlRegistry, ()> for State {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &wayland_client::Connection,
qh: &wayland_client::QueueHandle<Self>,
) {
if let wl_registry::Event::Global {
name,
interface,
version,
} = event
{
if interface.as_str() == "wl_seat" {
state.wl_seat = true;
registry.bind::<wl_seat::WlSeat, _, _>(name, version, qh, ());
}
}
}
}
impl wayland_client::Dispatch<wl_seat::WlSeat, ()> for State {
fn event(
state: &mut Self,
seat: &wl_seat::WlSeat,
event: wl_seat::Event,
_: &(),
_: &wayland_client::Connection,
qh: &wayland_client::QueueHandle<Self>,
) {
if let wl_seat::Event::Capabilities {
capabilities: wayland_client::WEnum::Value(capabilities),
} = event
{
if capabilities.contains(wl_seat::Capability::Keyboard) {
state.wl_keyboard = true;
seat.get_keyboard(qh, ());
}
}
}
}
impl wayland_client::Dispatch<wl_keyboard::WlKeyboard, ()> for State {
fn event(
state: &mut Self,
_: &wl_keyboard::WlKeyboard,
event: wl_keyboard::Event,
_: &(),
_: &wayland_client::Connection,
_: &wayland_client::QueueHandle<Self>,
) {
if let wl_keyboard::Event::Keymap { format, fd, size } = event {
match format {
wayland_client::WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) => {
let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let result = unsafe {
xkb::Keymap::new_from_fd(
&ctx,
fd,
size as usize,
xkb::KEYMAP_FORMAT_TEXT_V1,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
};
match result {
Ok(Some(keymap)) => state.keymap = Some(keymap),
Ok(None) => state.error = Some(KeymapError::FailedToCreateKeymap),
Err(e) => state.error = Some(KeymapError::Io(e)),
}
}
other => state.error = Some(KeymapError::UnsupportedKeymapFormat(other)),
}
}
}
}
wayland_client::delegate_noop!(State: ignore wayland_client::protocol::wl_compositor::WlCompositor);
wayland_client::delegate_noop!(State: ignore wayland_client::protocol::wl_surface::WlSurface);
wayland_client::delegate_noop!(State: ignore wayland_client::protocol::wl_shm::WlShm);
wayland_client::delegate_noop!(State: ignore wayland_client::protocol::wl_shm_pool::WlShmPool);
wayland_client::delegate_noop!(State: ignore wayland_client::protocol::wl_buffer::WlBuffer);