use std::{cell::RefCell, rc::Rc};
use std::{convert::TryInto, env};
use wayland_client::{
protocol::{
wl_keyboard::{KeymapFormat, WlKeyboard},
wl_seat::WlSeat,
},
DispatchData, Main,
};
use xkb::x11::{MIN_MAJOR_XKB_VERSION, MIN_MINOR_XKB_VERSION};
use xkbcommon::xkb;
use xkbcommon::xkb::{KEYMAP_COMPILE_NO_FLAGS, KEYMAP_FORMAT_TEXT_V1};
use crate::errors::KeyLayoutError;
pub struct KeyLayout {
keymap: xkb::Keymap,
}
impl KeyLayout {
pub fn new_from_window(window: &winit::window::Window) -> Result<KeyLayout, KeyLayoutError> {
if let Some(_conn) = winit::platform::unix::WindowExtUnix::xcb_connection(window) {
Self::_new_x11()
} else {
Self::_new_wayland()
}
}
pub fn get_key_as_string(&self, scancode: u32) -> String {
let state = xkb::State::new(&self.keymap);
let keysym = state.key_get_one_sym(scancode + 8);
let mut output = state.key_get_utf8(scancode + 8);
output = output.replace(|c: char| c.is_control(), "");
if output.trim().is_empty() {
output = xkb::keysym_get_name(keysym);
} else {
output.make_ascii_uppercase();
}
output
}
fn _new_wayland() -> Result<Self, KeyLayoutError> {
let display = wayland_client::Display::connect_to_env().unwrap();
let mut event_queue = display.create_event_queue();
let token = event_queue.token();
let proxy = &*display;
let attached = proxy.attach(token);
let registry = attached.get_registry();
let available_interfaces = Rc::new(RefCell::new(Vec::<(u32, String, u32)>::new()));
let available_interfaces_copy = Rc::clone(&available_interfaces);
registry.quick_assign(move |_reg, event, _data| {
if let wayland_client::protocol::wl_registry::Event::Global {
name,
interface,
version,
} = event
{
(*available_interfaces_copy)
.borrow_mut()
.push((name, interface, version));
}
});
event_queue
.sync_roundtrip(&mut (), |_, _, _| {})
.expect("Error during event queue roundtrip");
let (seat_name, _seat_interface, seat_version) = (*available_interfaces)
.borrow()
.iter()
.find(|(_name, interface, _version)| interface == "wl_seat")
.expect("wl_seat not found in available interfaces")
.clone();
attached.sync();
let wl_seat = registry.bind::<WlSeat>(seat_version, seat_name);
let capabilities = Rc::new(RefCell::new(
wayland_client::protocol::wl_seat::Capability::empty(),
));
let capabilities_copy = Rc::clone(&capabilities);
wl_seat.quick_assign(move |_seat, event, _data| {
if let wayland_client::protocol::wl_seat::Event::Capabilities { capabilities } = event {
(*capabilities_copy).borrow_mut().set(capabilities, true);
}
});
event_queue
.sync_roundtrip(&mut (), |_, _, _| {})
.expect("Error during event queue roundtrip");
if !(*capabilities)
.borrow()
.contains(wayland_client::protocol::wl_seat::Capability::Keyboard)
{
panic!("wl_seat does not have keyboard capability");
}
let wl_keyboard = wl_seat.get_keyboard();
let file_descriptor = Rc::new(RefCell::new(-1));
let size = Rc::new(RefCell::new(0));
let file_descriptor_copy = Rc::clone(&file_descriptor);
let size_copy = Rc::clone(&size);
wl_keyboard.quick_assign(
move |_object: Main<WlKeyboard>,
event: wayland_client::protocol::wl_keyboard::Event,
_data: DispatchData<'_>| {
if let wayland_client::protocol::wl_keyboard::Event::Keymap { format, fd, size } =
event
{
match format {
KeymapFormat::XkbV1 => {
*file_descriptor_copy.borrow_mut() = fd;
*size_copy.borrow_mut() = size;
}
KeymapFormat::NoKeymap => {
panic!("NoKeymap format");
}
_ => {
panic!("Keymap Format not supported");
}
};
}
},
);
event_queue.sync_roundtrip(&mut (), |_, _, _| {}).unwrap();
let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = unsafe {
xkb::Keymap::new_from_fd(
&ctx,
*file_descriptor.borrow(),
(*size.borrow()).try_into().unwrap(),
KEYMAP_FORMAT_TEXT_V1,
KEYMAP_COMPILE_NO_FLAGS,
)
}
.ok()
.flatten()
.expect("Failed to create keymap.");
Ok(Self { keymap })
}
fn _new_x11() -> Result<Self, KeyLayoutError> {
let (conn, _) = xcb::Connection::connect(None).unwrap();
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(
&conn,
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(&conn);
let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let keymap = xkb::x11::keymap_new_from_device(&ctx, &conn, device_id, 0);
Ok(KeyLayout { keymap })
}
pub fn new() -> Result<KeyLayout, KeyLayoutError> {
match env::var("XDG_SESSION_TYPE") {
Ok(session_type) => match session_type.as_str() {
"wayland" => Self::_new_wayland(),
"x11" => Self::_new_x11(),
_ => Err(KeyLayoutError::SessionError),
},
Err(_e) => Err(KeyLayoutError::SessionError),
}
}
}
#[cfg(target_os = "linux")]
pub trait KeyLayoutExtUnix {
fn new_wayland() -> Result<KeyLayout, KeyLayoutError>;
fn new_x11() -> Result<KeyLayout, KeyLayoutError>;
}
impl KeyLayoutExtUnix for KeyLayout {
fn new_wayland() -> Result<KeyLayout, KeyLayoutError> {
Self::_new_wayland()
}
fn new_x11() -> Result<KeyLayout, KeyLayoutError> {
Self::_new_x11()
}
}