On X11, use events modifiers to detect state
While there's a separate event to deliver modifiers for keyboard, unfortunately, it's not even remotely reflects the modifiers state. Thus use events along side regular modifier updates to correctly detect the state. Also, apply the modifiers from the regular key event by converting their state to xkb modifiers state. Links: https://github.com/alacritty/alacritty/issues/7549 Closes: #3388
This commit is contained in:
parent
db41938deb
commit
e61a7320a2
12 changed files with 1166 additions and 775 deletions
|
|
@ -1,2 +1 @@
|
|||
pub mod keymap;
|
||||
pub mod xkb_state;
|
||||
pub mod xkb;
|
||||
|
|
|
|||
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
124
src/platform_impl/linux/common/xkb/compose.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
//! XKB compose handling.
|
||||
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use super::XkbContext;
|
||||
use super::XKBCH;
|
||||
use smol_str::SmolStr;
|
||||
use xkbcommon_dl::{
|
||||
xkb_compose_compile_flags, xkb_compose_feed_result, xkb_compose_state, xkb_compose_state_flags,
|
||||
xkb_compose_status, xkb_compose_table, xkb_keysym_t,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeTable {
|
||||
table: NonNull<xkb_compose_table>,
|
||||
}
|
||||
|
||||
impl XkbComposeTable {
|
||||
pub fn new(context: &XkbContext) -> Option<Self> {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let table = unsafe {
|
||||
(XKBCH.xkb_compose_table_new_from_locale)(
|
||||
context.as_ptr(),
|
||||
locale.as_ptr(),
|
||||
xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let table = NonNull::new(table)?;
|
||||
Some(Self { table })
|
||||
}
|
||||
|
||||
/// Create new state with the given compose table.
|
||||
pub fn new_state(&self) -> Option<XkbComposeState> {
|
||||
let state = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
self.table.as_ptr(),
|
||||
xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
let state = NonNull::new(state)?;
|
||||
Some(XkbComposeState { state })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbComposeTable {
|
||||
type Target = NonNull<xkb_compose_table>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.table
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeTable {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_table_unref)(self.table.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbComposeState {
|
||||
state: NonNull<xkb_compose_state>,
|
||||
}
|
||||
|
||||
impl XkbComposeState {
|
||||
pub fn get_string(&mut self, scratch_buffer: &mut Vec<u8>) -> Option<SmolStr> {
|
||||
super::make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBCH.xkb_compose_state_get_utf8)(self.state.as_ptr(), ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn feed(&mut self, keysym: xkb_keysym_t) -> ComposeStatus {
|
||||
let feed_result = unsafe { (XKBCH.xkb_compose_state_feed)(self.state.as_ptr(), keysym) };
|
||||
match feed_result {
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED => ComposeStatus::Ignored,
|
||||
xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED => {
|
||||
ComposeStatus::Accepted(self.status())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_reset)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn status(&mut self) -> xkb_compose_status {
|
||||
unsafe { (XKBCH.xkb_compose_state_get_status)(self.state.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbComposeState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBCH.xkb_compose_state_unref)(self.state.as_ptr());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ComposeStatus {
|
||||
Accepted(xkb_compose_status),
|
||||
Ignored,
|
||||
None,
|
||||
}
|
||||
|
|
@ -1,6 +1,24 @@
|
|||
//! Convert XKB keys to Winit keys.
|
||||
//! XKB keymap.
|
||||
|
||||
use std::ffi::c_char;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::{self, NonNull};
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
use x11_dl::xlib_xcb::xcb_connection_t;
|
||||
#[cfg(wayland_platform)]
|
||||
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
|
||||
|
||||
use xkb::XKB_MOD_INVALID;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_keycode_t, xkb_keymap, xkb_keymap_compile_flags, xkb_keysym_t,
|
||||
xkb_layout_index_t, xkb_mod_index_t,
|
||||
};
|
||||
|
||||
use crate::keyboard::{Key, KeyCode, KeyLocation, NamedKey, NativeKey, NativeKeyCode, PhysicalKey};
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform_impl::common::xkb::XKBXH;
|
||||
use crate::platform_impl::common::xkb::{XkbContext, XKBH};
|
||||
|
||||
/// Map the raw X11-style keycode to the `KeyCode` enum.
|
||||
///
|
||||
|
|
@ -894,3 +912,140 @@ pub fn keysym_location(keysym: u32) -> KeyLocation {
|
|||
_ => KeyLocation::Standard,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbKeymap {
|
||||
keymap: NonNull<xkb_keymap>,
|
||||
_mods_indices: ModsIndices,
|
||||
pub _core_keyboard_id: i32,
|
||||
}
|
||||
|
||||
impl XkbKeymap {
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn from_fd(context: &XkbContext, fd: OwnedFd, size: usize) -> Option<Self> {
|
||||
let map = unsafe { MmapOptions::new().len(size).map_copy_read_only(&fd).ok()? };
|
||||
|
||||
let keymap = unsafe {
|
||||
let keymap = (XKBH.xkb_keymap_new_from_string)(
|
||||
(*context).as_ptr(),
|
||||
map.as_ptr() as *const _,
|
||||
xkb::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
NonNull::new(keymap)?
|
||||
};
|
||||
|
||||
Some(Self::new_inner(keymap, 0))
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn from_x11_keymap(
|
||||
context: &XkbContext,
|
||||
xcb: *mut xcb_connection_t,
|
||||
core_keyboard_id: i32,
|
||||
) -> Option<Self> {
|
||||
let keymap = unsafe {
|
||||
(XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
context.as_ptr(),
|
||||
xcb,
|
||||
core_keyboard_id,
|
||||
xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
let keymap = NonNull::new(keymap)?;
|
||||
Some(Self::new_inner(keymap, core_keyboard_id))
|
||||
}
|
||||
|
||||
fn new_inner(keymap: NonNull<xkb_keymap>, _core_keyboard_id: i32) -> Self {
|
||||
let mods_indices = ModsIndices {
|
||||
shift: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_SHIFT),
|
||||
caps: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CAPS),
|
||||
ctrl: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_CTRL),
|
||||
alt: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_ALT),
|
||||
num: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_NUM),
|
||||
mod3: mod_index_for_name(keymap, b"Mod3\0"),
|
||||
logo: mod_index_for_name(keymap, xkb::XKB_MOD_NAME_LOGO),
|
||||
mod5: mod_index_for_name(keymap, b"Mod5\0"),
|
||||
};
|
||||
|
||||
Self {
|
||||
keymap,
|
||||
_mods_indices: mods_indices,
|
||||
_core_keyboard_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn mods_indices(&self) -> ModsIndices {
|
||||
self._mods_indices
|
||||
}
|
||||
|
||||
pub fn first_keysym_by_level(
|
||||
&mut self,
|
||||
layout: xkb_layout_index_t,
|
||||
keycode: xkb_keycode_t,
|
||||
) -> xkb_keysym_t {
|
||||
unsafe {
|
||||
let mut keysyms = ptr::null();
|
||||
let count = (XKBH.xkb_keymap_key_get_syms_by_level)(
|
||||
self.keymap.as_ptr(),
|
||||
keycode,
|
||||
layout,
|
||||
// NOTE: The level should be zero to ignore modifiers.
|
||||
0,
|
||||
&mut keysyms,
|
||||
);
|
||||
|
||||
if count == 1 {
|
||||
*keysyms
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the given key repeats.
|
||||
pub fn key_repeats(&mut self, keycode: xkb_keycode_t) -> bool {
|
||||
unsafe { (XKBH.xkb_keymap_key_repeats)(self.keymap.as_ptr(), keycode) == 1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbKeymap {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_keymap_unref)(self.keymap.as_ptr());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbKeymap {
|
||||
type Target = NonNull<xkb_keymap>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.keymap
|
||||
}
|
||||
}
|
||||
|
||||
/// Modifier index in the keymap.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct ModsIndices {
|
||||
pub shift: Option<xkb_mod_index_t>,
|
||||
pub caps: Option<xkb_mod_index_t>,
|
||||
pub ctrl: Option<xkb_mod_index_t>,
|
||||
pub alt: Option<xkb_mod_index_t>,
|
||||
pub num: Option<xkb_mod_index_t>,
|
||||
pub mod3: Option<xkb_mod_index_t>,
|
||||
pub logo: Option<xkb_mod_index_t>,
|
||||
pub mod5: Option<xkb_mod_index_t>,
|
||||
}
|
||||
|
||||
fn mod_index_for_name(keymap: NonNull<xkb_keymap>, name: &[u8]) -> Option<xkb_mod_index_t> {
|
||||
unsafe {
|
||||
let mod_index =
|
||||
(XKBH.xkb_keymap_mod_get_index)(keymap.as_ptr(), name.as_ptr() as *const c_char);
|
||||
if mod_index == XKB_MOD_INVALID {
|
||||
None
|
||||
} else {
|
||||
Some(mod_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
461
src/platform_impl/linux/common/xkb/mod.rs
Normal file
461
src/platform_impl/linux/common/xkb/mod.rs
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
use std::convert::TryInto;
|
||||
use std::ops::Deref;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(wayland_platform)]
|
||||
use std::os::unix::io::OwnedFd;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_compose_status, xkb_context, xkb_context_flags, xkbcommon_compose_handle,
|
||||
xkbcommon_handle, XkbCommon, XkbCommonCompose,
|
||||
};
|
||||
#[cfg(x11_platform)]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
use crate::event::ElementState;
|
||||
use crate::event::KeyEvent;
|
||||
use crate::keyboard::{Key, KeyLocation};
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
|
||||
mod compose;
|
||||
mod keymap;
|
||||
mod state;
|
||||
|
||||
use compose::{ComposeStatus, XkbComposeState, XkbComposeTable};
|
||||
use keymap::XkbKeymap;
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub use keymap::raw_keycode_to_physicalkey;
|
||||
pub use keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub use state::XkbState;
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
|
||||
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
|
||||
#[cfg(feature = "x11")]
|
||||
static XKBXH: Lazy<&'static xkb::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset_dead_keys() {
|
||||
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
// NOTE: field order matters.
|
||||
#[cfg(x11_platform)]
|
||||
pub core_keyboard_id: i32,
|
||||
state: Option<XkbState>,
|
||||
keymap: Option<XkbKeymap>,
|
||||
compose_state1: Option<XkbComposeState>,
|
||||
compose_state2: Option<XkbComposeState>,
|
||||
_compose_table: Option<XkbComposeTable>,
|
||||
context: XkbContext,
|
||||
scratch_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
if xkb::xkbcommon_option().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let context = XkbContext::new()?;
|
||||
let mut compose_table = XkbComposeTable::new(&context);
|
||||
let mut compose_state1 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
let mut compose_state2 = compose_table.as_ref().and_then(|table| table.new_state());
|
||||
|
||||
// Disable compose if anything compose related failed to initialize.
|
||||
if compose_table.is_none() || compose_state1.is_none() || compose_state2.is_none() {
|
||||
compose_state2 = None;
|
||||
compose_state1 = None;
|
||||
compose_table = None;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
state: None,
|
||||
keymap: None,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
#[cfg(x11_platform)]
|
||||
core_keyboard_id: 0,
|
||||
_compose_table: compose_table,
|
||||
context,
|
||||
scratch_buffer: Vec::with_capacity(8),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn from_x11_xkb(xcb: *mut xcb_connection_t) -> Result<Self, Error> {
|
||||
let result = unsafe {
|
||||
(XKBXH.xkb_x11_setup_xkb_extension)(
|
||||
xcb,
|
||||
1,
|
||||
2,
|
||||
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if result != 1 {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut this = Self::new()?;
|
||||
this.core_keyboard_id = unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(xcb) };
|
||||
this.set_keymap_from_x11(xcb);
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn state_mut(&mut self) -> Option<&mut XkbState> {
|
||||
self.state.as_mut()
|
||||
}
|
||||
|
||||
pub fn keymap_mut(&mut self) -> Option<&mut XkbKeymap> {
|
||||
self.keymap.as_mut()
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn set_keymap_from_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
let keymap = XkbKeymap::from_fd(&self.context, fd, size);
|
||||
let state = keymap.as_ref().and_then(XkbState::new_wayland);
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn set_keymap_from_x11(&mut self, xcb: *mut xcb_connection_t) {
|
||||
let keymap = XkbKeymap::from_x11_keymap(&self.context, xcb, self.core_keyboard_id);
|
||||
let state = keymap
|
||||
.as_ref()
|
||||
.and_then(|keymap| XkbState::new_x11(xcb, keymap));
|
||||
if keymap.is_none() || state.is_none() {
|
||||
warn!("failed to update xkb keymap");
|
||||
}
|
||||
self.state = state;
|
||||
self.keymap = keymap;
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
pub fn key_context(&mut self) -> Option<KeyContext<'_>> {
|
||||
let state = self.state.as_mut()?;
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Key builder context with the user provided xkb state.
|
||||
///
|
||||
/// Should be used when the original context must not be altered.
|
||||
#[cfg(x11_platform)]
|
||||
pub fn key_context_with_state<'a>(
|
||||
&'a mut self,
|
||||
state: &'a mut XkbState,
|
||||
) -> Option<KeyContext<'a>> {
|
||||
let keymap = self.keymap.as_mut()?;
|
||||
let compose_state1 = self.compose_state1.as_mut();
|
||||
let compose_state2 = self.compose_state2.as_mut();
|
||||
let scratch_buffer = &mut self.scratch_buffer;
|
||||
Some(KeyContext {
|
||||
state,
|
||||
keymap,
|
||||
compose_state1,
|
||||
compose_state2,
|
||||
scratch_buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyContext<'a> {
|
||||
pub state: &'a mut XkbState,
|
||||
pub keymap: &'a mut XkbKeymap,
|
||||
compose_state1: Option<&'a mut XkbComposeState>,
|
||||
compose_state2: Option<&'a mut XkbComposeState>,
|
||||
scratch_buffer: &'a mut Vec<u8>,
|
||||
}
|
||||
|
||||
impl<'a> KeyContext<'a> {
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) -> KeyEvent {
|
||||
let mut event =
|
||||
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
|
||||
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
|
||||
let (logical_key, location) = event.key();
|
||||
let text = event.text();
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra {
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
};
|
||||
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
platform_specific,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
self.scratch_buffer.clear();
|
||||
self.scratch_buffer.reserve(8);
|
||||
loop {
|
||||
let bytes_written = unsafe {
|
||||
(XKBH.xkb_keysym_to_utf8)(
|
||||
keysym,
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
)
|
||||
};
|
||||
if bytes_written == 0 {
|
||||
return None;
|
||||
} else if bytes_written == -1 {
|
||||
self.scratch_buffer.reserve(8);
|
||||
} else {
|
||||
unsafe {
|
||||
self.scratch_buffer
|
||||
.set_len(bytes_written.try_into().unwrap())
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the null-terminator
|
||||
self.scratch_buffer.pop();
|
||||
byte_slice_to_smol_str(self.scratch_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventResults<'a, 'b> {
|
||||
context: &'a mut KeyContext<'b>,
|
||||
keycode: u32,
|
||||
keysym: u32,
|
||||
compose: ComposeStatus,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventResults<'a, 'b> {
|
||||
fn new(context: &'a mut KeyContext<'b>, keycode: u32, compose: bool) -> Self {
|
||||
let keysym = context.state.get_one_sym_raw(keycode);
|
||||
|
||||
let compose = if let Some(state) = context.compose_state1.as_mut().filter(|_| compose) {
|
||||
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
|
||||
state.reset();
|
||||
context.compose_state2.as_mut().unwrap().reset();
|
||||
}
|
||||
state.feed(keysym)
|
||||
} else {
|
||||
ComposeStatus::None
|
||||
};
|
||||
|
||||
KeyEventResults {
|
||||
context,
|
||||
keycode,
|
||||
keysym,
|
||||
compose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
let (key, location) = match self.keysym_to_key(self.keysym) {
|
||||
Ok(known) => return known,
|
||||
Err(undefined) => undefined,
|
||||
};
|
||||
|
||||
if let ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSING) = self.compose {
|
||||
let compose_state = self.context.compose_state2.as_mut().unwrap();
|
||||
// When pressing a dead key twice, the non-combining variant of that character will
|
||||
// be produced. Since this function only concerns itself with a single keypress, we
|
||||
// simulate this double press here by feeding the keysym to the compose state
|
||||
// twice.
|
||||
|
||||
compose_state.feed(self.keysym);
|
||||
if matches!(compose_state.feed(self.keysym), ComposeStatus::Accepted(_)) {
|
||||
// Extracting only a single `char` here *should* be fine, assuming that no
|
||||
// dead key's non-combining variant ever occupies more than one `char`.
|
||||
let text = compose_state.get_string(self.context.scratch_buffer);
|
||||
let key = Key::Dead(text.and_then(|s| s.chars().next()));
|
||||
(key, location)
|
||||
} else {
|
||||
(key, location)
|
||||
}
|
||||
} else {
|
||||
let key = self
|
||||
.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
|
||||
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
|
||||
let layout = self.context.state.layout(self.keycode);
|
||||
let keysym = self
|
||||
.context
|
||||
.keymap
|
||||
.first_keysym_by_level(layout, self.keycode);
|
||||
|
||||
match self.keysym_to_key(keysym) {
|
||||
Ok((key, location)) => (key, location),
|
||||
Err((key, location)) => {
|
||||
let key = self
|
||||
.context
|
||||
.keysym_to_utf8_raw(keysym)
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key);
|
||||
(key, location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = keymap::keysym_location(keysym);
|
||||
let key = keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
Err((key, location))
|
||||
} else {
|
||||
Ok((key, location))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> Option<SmolStr> {
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.context.keysym_to_utf8_raw(self.keysym))
|
||||
}
|
||||
|
||||
// The current behaviour makes it so composing a character overrides attempts to input a
|
||||
// control character with the `Ctrl` key. We can potentially add a configuration option
|
||||
// if someone specifically wants the oppsite behaviour.
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
match self.composed_text() {
|
||||
Ok(text) => text,
|
||||
Err(_) => self
|
||||
.context
|
||||
.state
|
||||
.get_utf8_raw(self.keycode, self.context.scratch_buffer),
|
||||
}
|
||||
}
|
||||
|
||||
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
|
||||
match self.compose {
|
||||
ComposeStatus::Accepted(xkb_compose_status::XKB_COMPOSE_COMPOSED) => {
|
||||
let state = self.context.compose_state1.as_mut().unwrap();
|
||||
Ok(state.get_string(self.context.scratch_buffer))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbContext {
|
||||
context: NonNull<xkb_context>,
|
||||
}
|
||||
|
||||
impl XkbContext {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let context = unsafe { (XKBH.xkb_context_new)(xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
|
||||
|
||||
let context = match NonNull::new(context) {
|
||||
Some(context) => context,
|
||||
None => return Err(Error::XKBNotFound),
|
||||
};
|
||||
|
||||
Ok(Self { context })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_context_unref)(self.context.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for XkbContext {
|
||||
type Target = NonNull<xkb_context>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.context
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
|
||||
/// `xkb_state_key_get_utf8`.
|
||||
fn make_string_with<F>(scratch_buffer: &mut Vec<u8>, mut f: F) -> Option<SmolStr>
|
||||
where
|
||||
F: FnMut(*mut c_char, usize) -> i32,
|
||||
{
|
||||
let size = f(ptr::null_mut(), 0);
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
let size = usize::try_from(size).unwrap();
|
||||
scratch_buffer.clear();
|
||||
// The allocated buffer must include space for the null-terminator.
|
||||
scratch_buffer.reserve(size + 1);
|
||||
unsafe {
|
||||
let written = f(
|
||||
scratch_buffer.as_mut_ptr().cast(),
|
||||
scratch_buffer.capacity(),
|
||||
);
|
||||
if usize::try_from(written).unwrap() != size {
|
||||
// This will likely never happen.
|
||||
return None;
|
||||
}
|
||||
scratch_buffer.set_len(size);
|
||||
};
|
||||
|
||||
byte_slice_to_smol_str(scratch_buffer)
|
||||
}
|
||||
|
||||
// NOTE: This is track_caller so we can have more informative line numbers when logging
|
||||
#[track_caller]
|
||||
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
|
||||
std::str::from_utf8(bytes)
|
||||
.map(SmolStr::new)
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
|
||||
bytes
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
189
src/platform_impl/linux/common/xkb/state.rs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
//! XKB state.
|
||||
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
#[cfg(x11_platform)]
|
||||
use x11_dl::xlib_xcb::xcb_connection_t;
|
||||
use xkbcommon_dl::{
|
||||
self as xkb, xkb_keycode_t, xkb_keysym_t, xkb_layout_index_t, xkb_state, xkb_state_component,
|
||||
};
|
||||
|
||||
use crate::platform_impl::common::xkb::keymap::XkbKeymap;
|
||||
#[cfg(x11_platform)]
|
||||
use crate::platform_impl::common::xkb::XKBXH;
|
||||
use crate::platform_impl::common::xkb::{make_string_with, XKBH};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct XkbState {
|
||||
state: NonNull<xkb_state>,
|
||||
modifiers: ModifiersState,
|
||||
}
|
||||
|
||||
impl XkbState {
|
||||
#[cfg(wayland_platform)]
|
||||
pub fn new_wayland(keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = NonNull::new(unsafe { (XKBH.xkb_state_new)(keymap.as_ptr()) })?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn new_x11(xcb: *mut xcb_connection_t, keymap: &XkbKeymap) -> Option<Self> {
|
||||
let state = unsafe {
|
||||
(XKBXH.xkb_x11_state_new_from_device)(keymap.as_ptr(), xcb, keymap._core_keyboard_id)
|
||||
};
|
||||
let state = NonNull::new(state)?;
|
||||
Some(Self::new_inner(state))
|
||||
}
|
||||
|
||||
fn new_inner(state: NonNull<xkb_state>) -> Self {
|
||||
let modifiers = ModifiersState::default();
|
||||
let mut this = Self { state, modifiers };
|
||||
this.reload_modifiers();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get_one_sym_raw(&mut self, keycode: xkb_keycode_t) -> xkb_keysym_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.state.as_ptr(), keycode) }
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, key: xkb_keycode_t) -> xkb_layout_index_t {
|
||||
unsafe { (XKBH.xkb_state_key_get_layout)(self.state.as_ptr(), key) }
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn depressed_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_DEPRESSED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn latched_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LATCHED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(x11_platform)]
|
||||
pub fn locked_modifiers(&mut self) -> xkb::xkb_mod_mask_t {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_serialize_mods)(
|
||||
self.state.as_ptr(),
|
||||
xkb_state_component::XKB_STATE_MODS_LOCKED,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_utf8_raw(
|
||||
&mut self,
|
||||
keycode: xkb_keycode_t,
|
||||
scratch_buffer: &mut Vec<u8>,
|
||||
) -> Option<SmolStr> {
|
||||
make_string_with(scratch_buffer, |ptr, len| unsafe {
|
||||
(XKBH.xkb_state_key_get_utf8)(self.state.as_ptr(), keycode, ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn modifiers(&self) -> ModifiersState {
|
||||
self.modifiers
|
||||
}
|
||||
|
||||
pub fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
depressed_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
let mask = unsafe {
|
||||
(XKBH.xkb_state_update_mask)(
|
||||
self.state.as_ptr(),
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
depressed_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
)
|
||||
};
|
||||
|
||||
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
|
||||
// Effective value of mods have changed, we need to update our state.
|
||||
self.reload_modifiers();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload the modifiers.
|
||||
fn reload_modifiers(&mut self) {
|
||||
self.modifiers.ctrl = self.mod_name_is_active(xkb::XKB_MOD_NAME_CTRL);
|
||||
self.modifiers.alt = self.mod_name_is_active(xkb::XKB_MOD_NAME_ALT);
|
||||
self.modifiers.shift = self.mod_name_is_active(xkb::XKB_MOD_NAME_SHIFT);
|
||||
self.modifiers.caps_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_CAPS);
|
||||
self.modifiers.logo = self.mod_name_is_active(xkb::XKB_MOD_NAME_LOGO);
|
||||
self.modifiers.num_lock = self.mod_name_is_active(xkb::XKB_MOD_NAME_NUM);
|
||||
}
|
||||
|
||||
/// Check if the modifier is active within xkb.
|
||||
fn mod_name_is_active(&mut self, name: &[u8]) -> bool {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_mod_name_is_active)(
|
||||
self.state.as_ptr(),
|
||||
name.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
|
||||
) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XkbState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(XKBH.xkb_state_unref)(self.state.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for crate::keyboard::ModifiersState {
|
||||
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
|
||||
let mut to_mods = crate::keyboard::ModifiersState::empty();
|
||||
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
|
||||
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
|
||||
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
|
||||
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
|
||||
to_mods
|
||||
}
|
||||
}
|
||||
|
|
@ -1,692 +0,0 @@
|
|||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use smol_str::SmolStr;
|
||||
use xkbcommon_dl::{
|
||||
self as ffi, xkb_state_component, xkbcommon_compose_handle, xkbcommon_handle, XkbCommon,
|
||||
XkbCommonCompose,
|
||||
};
|
||||
#[cfg(feature = "wayland")]
|
||||
use {memmap2::MmapOptions, std::os::unix::io::OwnedFd};
|
||||
#[cfg(feature = "x11")]
|
||||
use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::xkbcommon_x11_handle};
|
||||
|
||||
use crate::event::KeyEvent;
|
||||
use crate::platform_impl::common::keymap;
|
||||
use crate::platform_impl::KeyEventExtra;
|
||||
use crate::{
|
||||
event::ElementState,
|
||||
keyboard::{Key, KeyLocation, PhysicalKey},
|
||||
};
|
||||
|
||||
// TODO: Wire this up without using a static `AtomicBool`.
|
||||
static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset_dead_keys() {
|
||||
RESET_DEAD_KEYS.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
static XKBH: Lazy<&'static XkbCommon> = Lazy::new(xkbcommon_handle);
|
||||
static XKBCH: Lazy<&'static XkbCommonCompose> = Lazy::new(xkbcommon_compose_handle);
|
||||
#[cfg(feature = "x11")]
|
||||
static XKBXH: Lazy<&'static ffi::x11::XkbCommonX11> = Lazy::new(xkbcommon_x11_handle);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KbdState {
|
||||
#[cfg(feature = "x11")]
|
||||
xcb_connection: *mut xcb_connection_t,
|
||||
xkb_context: *mut ffi::xkb_context,
|
||||
xkb_keymap: *mut ffi::xkb_keymap,
|
||||
xkb_state: *mut ffi::xkb_state,
|
||||
xkb_compose_table: *mut ffi::xkb_compose_table,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
xkb_compose_state_2: *mut ffi::xkb_compose_state,
|
||||
mods_state: ModifiersState,
|
||||
#[cfg(feature = "x11")]
|
||||
pub core_keyboard_id: i32,
|
||||
scratch_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl KbdState {
|
||||
pub fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
depressed_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
if !self.ready() {
|
||||
return;
|
||||
}
|
||||
let mask = unsafe {
|
||||
(XKBH.xkb_state_update_mask)(
|
||||
self.xkb_state,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
depressed_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
)
|
||||
};
|
||||
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
|
||||
// effective value of mods have changed, we need to update our state
|
||||
self.mods_state.update_with(self.xkb_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
|
||||
if !self.ready() {
|
||||
return 0;
|
||||
}
|
||||
unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) }
|
||||
}
|
||||
|
||||
pub fn get_utf8_raw(&mut self, keycode: u32) -> Option<SmolStr> {
|
||||
if !self.ready() {
|
||||
return None;
|
||||
}
|
||||
let xkb_state = self.xkb_state;
|
||||
self.make_string_with({
|
||||
|ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) }
|
||||
})
|
||||
}
|
||||
|
||||
fn compose_feed_normal(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
|
||||
self.compose_feed(self.xkb_compose_state, keysym)
|
||||
}
|
||||
|
||||
fn compose_feed_2(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
|
||||
self.compose_feed(self.xkb_compose_state_2, keysym)
|
||||
}
|
||||
|
||||
fn compose_feed(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
keysym: u32,
|
||||
) -> Option<ffi::xkb_compose_feed_result> {
|
||||
if !self.ready() || self.xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) {
|
||||
unsafe { self.init_compose() };
|
||||
}
|
||||
Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) })
|
||||
}
|
||||
|
||||
fn compose_status_normal(&mut self) -> Option<ffi::xkb_compose_status> {
|
||||
self.compose_status(self.xkb_compose_state)
|
||||
}
|
||||
|
||||
fn compose_status(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
) -> Option<ffi::xkb_compose_status> {
|
||||
if !self.ready() || xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) })
|
||||
}
|
||||
|
||||
fn compose_get_utf8_normal(&mut self) -> Option<SmolStr> {
|
||||
self.compose_get_utf8(self.xkb_compose_state)
|
||||
}
|
||||
|
||||
fn compose_get_utf8_2(&mut self) -> Option<SmolStr> {
|
||||
self.compose_get_utf8(self.xkb_compose_state_2)
|
||||
}
|
||||
|
||||
fn compose_get_utf8(
|
||||
&mut self,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
) -> Option<SmolStr> {
|
||||
if !self.ready() || xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
self.make_string_with(|ptr, len| unsafe {
|
||||
(XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len)
|
||||
})
|
||||
}
|
||||
|
||||
/// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and
|
||||
/// `xkb_state_key_get_utf8`.
|
||||
fn make_string_with<F>(&mut self, mut f: F) -> Option<SmolStr>
|
||||
where
|
||||
F: FnMut(*mut c_char, usize) -> i32,
|
||||
{
|
||||
let size = f(ptr::null_mut(), 0);
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
let size = usize::try_from(size).unwrap();
|
||||
self.scratch_buffer.clear();
|
||||
// The allocated buffer must include space for the null-terminator
|
||||
self.scratch_buffer.reserve(size + 1);
|
||||
unsafe {
|
||||
let written = f(
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
);
|
||||
if usize::try_from(written).unwrap() != size {
|
||||
// This will likely never happen
|
||||
return None;
|
||||
}
|
||||
self.scratch_buffer.set_len(size);
|
||||
};
|
||||
byte_slice_to_smol_str(&self.scratch_buffer)
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
if ffi::xkbcommon_option().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let context =
|
||||
unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) };
|
||||
if context.is_null() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut me = Self {
|
||||
#[cfg(feature = "x11")]
|
||||
xcb_connection: ptr::null_mut(),
|
||||
xkb_context: context,
|
||||
xkb_keymap: ptr::null_mut(),
|
||||
xkb_state: ptr::null_mut(),
|
||||
xkb_compose_table: ptr::null_mut(),
|
||||
xkb_compose_state: ptr::null_mut(),
|
||||
xkb_compose_state_2: ptr::null_mut(),
|
||||
mods_state: ModifiersState::new(),
|
||||
#[cfg(feature = "x11")]
|
||||
core_keyboard_id: 0,
|
||||
scratch_buffer: Vec::new(),
|
||||
};
|
||||
|
||||
unsafe { me.init_compose() };
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result<Self, Error> {
|
||||
let mut me = Self::new()?;
|
||||
me.xcb_connection = connection;
|
||||
|
||||
let result = unsafe {
|
||||
(XKBXH.xkb_x11_setup_xkb_extension)(
|
||||
connection,
|
||||
1,
|
||||
2,
|
||||
xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
assert_eq!(result, 1, "Failed to initialize libxkbcommon");
|
||||
|
||||
unsafe { me.init_with_x11_keymap() };
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
unsafe fn init_compose(&mut self) {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let compose_table = unsafe {
|
||||
(XKBCH.xkb_compose_table_new_from_locale)(
|
||||
self.xkb_context,
|
||||
locale.as_ptr(),
|
||||
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_table.is_null() {
|
||||
// init of compose table failed, continue without compose
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_state.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state_2 = unsafe {
|
||||
(XKBCH.xkb_compose_state_new)(
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if compose_state_2.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
unsafe { (XKBCH.xkb_compose_table_unref)(compose_table) };
|
||||
unsafe { (XKBCH.xkb_compose_state_unref)(compose_state) };
|
||||
return;
|
||||
}
|
||||
|
||||
self.xkb_compose_table = compose_table;
|
||||
self.xkb_compose_state = compose_state;
|
||||
self.xkb_compose_state_2 = compose_state_2;
|
||||
}
|
||||
|
||||
unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) {
|
||||
self.xkb_keymap = keymap;
|
||||
self.xkb_state = state;
|
||||
self.mods_state.update_with(state);
|
||||
}
|
||||
|
||||
unsafe fn de_init(&mut self) {
|
||||
unsafe { (XKBH.xkb_state_unref)(self.xkb_state) };
|
||||
self.xkb_state = ptr::null_mut();
|
||||
unsafe { (XKBH.xkb_keymap_unref)(self.xkb_keymap) };
|
||||
self.xkb_keymap = ptr::null_mut();
|
||||
}
|
||||
|
||||
#[cfg(feature = "x11")]
|
||||
pub unsafe fn init_with_x11_keymap(&mut self) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
unsafe { self.de_init() };
|
||||
}
|
||||
|
||||
// TODO: Support keyboards other than the "virtual core keyboard device".
|
||||
self.core_keyboard_id =
|
||||
unsafe { (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection) };
|
||||
let keymap = unsafe {
|
||||
(XKBXH.xkb_x11_keymap_new_from_device)(
|
||||
self.xkb_context,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
if keymap.is_null() {
|
||||
panic!("Failed to get keymap from X11 server.");
|
||||
}
|
||||
|
||||
let state = unsafe {
|
||||
(XKBXH.xkb_x11_state_new_from_device)(
|
||||
keymap,
|
||||
self.xcb_connection,
|
||||
self.core_keyboard_id,
|
||||
)
|
||||
};
|
||||
unsafe { self.post_init(state, keymap) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) {
|
||||
if !self.xkb_keymap.is_null() {
|
||||
unsafe { self.de_init() };
|
||||
}
|
||||
|
||||
let map = unsafe {
|
||||
MmapOptions::new()
|
||||
.len(size)
|
||||
.map_copy_read_only(&fd)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let keymap = unsafe {
|
||||
(XKBH.xkb_keymap_new_from_string)(
|
||||
self.xkb_context,
|
||||
map.as_ptr() as *const _,
|
||||
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
};
|
||||
|
||||
if keymap.is_null() {
|
||||
panic!("Received invalid keymap from compositor.");
|
||||
}
|
||||
|
||||
let state = unsafe { (XKBH.xkb_state_new)(keymap) };
|
||||
unsafe { self.post_init(state, keymap) };
|
||||
}
|
||||
|
||||
pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool {
|
||||
unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ready(&self) -> bool {
|
||||
!self.xkb_state.is_null()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mods_state(&self) -> ModifiersState {
|
||||
self.mods_state
|
||||
}
|
||||
|
||||
pub fn process_key_event(
|
||||
&mut self,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) -> KeyEvent {
|
||||
let mut event =
|
||||
KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed);
|
||||
let physical_key = event.physical_key();
|
||||
let (logical_key, location) = event.key();
|
||||
let text = event.text();
|
||||
let (key_without_modifiers, _) = event.key_without_modifiers();
|
||||
let text_with_all_modifiers = event.text_with_all_modifiers();
|
||||
|
||||
let platform_specific = KeyEventExtra {
|
||||
text_with_all_modifiers,
|
||||
key_without_modifiers,
|
||||
};
|
||||
|
||||
KeyEvent {
|
||||
physical_key,
|
||||
logical_key,
|
||||
text,
|
||||
location,
|
||||
state,
|
||||
repeat,
|
||||
platform_specific,
|
||||
}
|
||||
}
|
||||
|
||||
fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<SmolStr> {
|
||||
self.scratch_buffer.clear();
|
||||
self.scratch_buffer.reserve(8);
|
||||
loop {
|
||||
let bytes_written = unsafe {
|
||||
(XKBH.xkb_keysym_to_utf8)(
|
||||
keysym,
|
||||
self.scratch_buffer.as_mut_ptr().cast(),
|
||||
self.scratch_buffer.capacity(),
|
||||
)
|
||||
};
|
||||
if bytes_written == 0 {
|
||||
return None;
|
||||
} else if bytes_written == -1 {
|
||||
self.scratch_buffer.reserve(8);
|
||||
} else {
|
||||
unsafe {
|
||||
self.scratch_buffer
|
||||
.set_len(bytes_written.try_into().unwrap())
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the null-terminator
|
||||
self.scratch_buffer.pop();
|
||||
byte_slice_to_smol_str(&self.scratch_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KbdState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.xkb_compose_state.is_null() {
|
||||
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state);
|
||||
}
|
||||
if !self.xkb_compose_state_2.is_null() {
|
||||
(XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2);
|
||||
}
|
||||
if !self.xkb_compose_table.is_null() {
|
||||
(XKBCH.xkb_compose_table_unref)(self.xkb_compose_table);
|
||||
}
|
||||
if !self.xkb_state.is_null() {
|
||||
(XKBH.xkb_state_unref)(self.xkb_state);
|
||||
}
|
||||
if !self.xkb_keymap.is_null() {
|
||||
(XKBH.xkb_keymap_unref)(self.xkb_keymap);
|
||||
}
|
||||
(XKBH.xkb_context_unref)(self.xkb_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventResults<'a> {
|
||||
state: &'a mut KbdState,
|
||||
keycode: u32,
|
||||
keysym: u32,
|
||||
compose: Option<XkbCompose>,
|
||||
}
|
||||
|
||||
impl<'a> KeyEventResults<'a> {
|
||||
fn new(state: &'a mut KbdState, keycode: u32, compose: bool) -> Self {
|
||||
let keysym = state.get_one_sym_raw(keycode);
|
||||
|
||||
let compose = if compose {
|
||||
Some(match state.compose_feed_normal(keysym) {
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => {
|
||||
// Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized.
|
||||
XkbCompose::Accepted(state.compose_status_normal().unwrap())
|
||||
}
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored,
|
||||
None => XkbCompose::Uninitialized,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
KeyEventResults {
|
||||
state,
|
||||
keycode,
|
||||
keysym,
|
||||
compose,
|
||||
}
|
||||
}
|
||||
|
||||
fn physical_key(&self) -> PhysicalKey {
|
||||
keymap::raw_keycode_to_physicalkey(self.keycode)
|
||||
}
|
||||
|
||||
pub fn key(&mut self) -> (Key, KeyLocation) {
|
||||
self.keysym_to_key(self.keysym)
|
||||
.unwrap_or_else(|(key, location)| match self.compose {
|
||||
Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => {
|
||||
// When pressing a dead key twice, the non-combining variant of that character will be
|
||||
// produced. Since this function only concerns itself with a single keypress, we simulate
|
||||
// this double press here by feeding the keysym to the compose state twice.
|
||||
self.state.compose_feed_2(self.keysym);
|
||||
match self.state.compose_feed_2(self.keysym) {
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => (
|
||||
// Extracting only a single `char` here *should* be fine, assuming that no dead
|
||||
// key's non-combining variant ever occupies more than one `char`.
|
||||
Key::Dead(
|
||||
self.state
|
||||
.compose_get_utf8_2()
|
||||
.map(|s| s.chars().next().unwrap()),
|
||||
),
|
||||
location,
|
||||
),
|
||||
_ => (key, location),
|
||||
}
|
||||
}
|
||||
_ => (
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key),
|
||||
location,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn key_without_modifiers(&mut self) -> (Key, KeyLocation) {
|
||||
// This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it.
|
||||
let mut keysyms = ptr::null();
|
||||
let keysym_count = unsafe {
|
||||
let layout = (XKBH.xkb_state_key_get_layout)(self.state.xkb_state, self.keycode);
|
||||
(XKBH.xkb_keymap_key_get_syms_by_level)(
|
||||
self.state.xkb_keymap,
|
||||
self.keycode,
|
||||
layout,
|
||||
// NOTE: The level should be zero to ignore modifiers.
|
||||
0,
|
||||
&mut keysyms,
|
||||
)
|
||||
};
|
||||
let keysym = if keysym_count == 1 {
|
||||
unsafe { *keysyms }
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.keysym_to_key(keysym)
|
||||
.unwrap_or_else(|(key, location)| {
|
||||
(
|
||||
self.state
|
||||
.keysym_to_utf8_raw(keysym)
|
||||
.map(Key::Character)
|
||||
.unwrap_or(key),
|
||||
location,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn keysym_to_key(&self, keysym: u32) -> Result<(Key, KeyLocation), (Key, KeyLocation)> {
|
||||
let location = super::keymap::keysym_location(keysym);
|
||||
let key = super::keymap::keysym_to_key(keysym);
|
||||
if matches!(key, Key::Unidentified(_)) {
|
||||
Err((key, location))
|
||||
} else {
|
||||
Ok((key, location))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&mut self) -> Option<SmolStr> {
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym))
|
||||
}
|
||||
|
||||
pub fn text_with_all_modifiers(&mut self) -> Option<SmolStr> {
|
||||
// The current behaviour makes it so composing a character overrides attempts to input a
|
||||
// control character with the `Ctrl` key. We can potentially add a configuration option
|
||||
// if someone specifically wants the oppsite behaviour.
|
||||
self.composed_text()
|
||||
.unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode))
|
||||
}
|
||||
|
||||
fn composed_text(&mut self) -> Result<Option<SmolStr>, ()> {
|
||||
if let Some(compose) = &self.compose {
|
||||
match compose {
|
||||
XkbCompose::Accepted(status) => match status {
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => {
|
||||
Ok(self.state.compose_get_utf8_normal())
|
||||
}
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()),
|
||||
_ => Ok(None),
|
||||
},
|
||||
XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()),
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn update_with(&mut self, state: *mut ffi::xkb_state) {
|
||||
let mod_name_is_active = |mod_name: &[u8]| unsafe {
|
||||
(XKBH.xkb_state_mod_name_is_active)(
|
||||
state,
|
||||
mod_name.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE,
|
||||
) > 0
|
||||
};
|
||||
self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL);
|
||||
self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT);
|
||||
self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT);
|
||||
self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS);
|
||||
self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO);
|
||||
self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModifiersState> for crate::keyboard::ModifiersState {
|
||||
fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState {
|
||||
let mut to_mods = crate::keyboard::ModifiersState::empty();
|
||||
to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift);
|
||||
to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl);
|
||||
to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt);
|
||||
to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo);
|
||||
to_mods
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum XkbCompose {
|
||||
Accepted(ffi::xkb_compose_status),
|
||||
Ignored,
|
||||
Uninitialized,
|
||||
}
|
||||
|
||||
// Note: This is track_caller so we can have more informative line numbers when logging
|
||||
#[track_caller]
|
||||
fn byte_slice_to_smol_str(bytes: &[u8]) -> Option<SmolStr> {
|
||||
std::str::from_utf8(bytes)
|
||||
.map(SmolStr::new)
|
||||
.map_err(|e| {
|
||||
log::warn!(
|
||||
"UTF-8 received from libxkbcommon ({:?}) was invalid: {e}",
|
||||
bytes
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
pub(crate) use self::common::keymap::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub(crate) use crate::cursor::OnlyCursorImageBuilder as PlatformCustomCursorBuilder;
|
||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||
pub(crate) use crate::platform_impl::Fullscreen;
|
||||
|
|
@ -516,7 +516,7 @@ impl Window {
|
|||
|
||||
#[inline]
|
||||
pub fn reset_dead_keys(&self) {
|
||||
common::xkb_state::reset_dead_keys()
|
||||
common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
|
|||
use crate::event::{ElementState, WindowEvent};
|
||||
use crate::keyboard::ModifiersState;
|
||||
|
||||
use crate::platform_impl::common::xkb_state::KbdState;
|
||||
use crate::platform_impl::common::xkb::Context;
|
||||
use crate::platform_impl::wayland::event_loop::sink::EventSink;
|
||||
use crate::platform_impl::wayland::seat::WinitSeatState;
|
||||
use crate::platform_impl::wayland::state::WinitState;
|
||||
|
|
@ -43,14 +43,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
|||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
}
|
||||
WlKeymapFormat::XkbV1 => unsafe {
|
||||
seat_state
|
||||
.keyboard_state
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.xkb_state
|
||||
.init_with_fd(fd, size as usize);
|
||||
},
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
|
|
@ -155,7 +151,12 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
|||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state.xkb_state.key_repeats(key) {
|
||||
if !keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +222,11 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
|||
|
||||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
if keyboard_state.repeat_info != RepeatInfo::Disable
|
||||
&& keyboard_state.xkb_state.key_repeats(key)
|
||||
&& keyboard_state
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.unwrap()
|
||||
.key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
|
|
@ -237,9 +242,14 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
|||
group,
|
||||
..
|
||||
} => {
|
||||
let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state;
|
||||
let xkb_context = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_context;
|
||||
let xkb_state = match xkb_context.state_mut() {
|
||||
Some(state) => state,
|
||||
None => return,
|
||||
};
|
||||
|
||||
xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
seat_state.modifiers = xkb_state.mods_state().into();
|
||||
seat_state.modifiers = xkb_state.modifiers().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
|
|
@ -285,7 +295,7 @@ pub struct KeyboardState {
|
|||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_state: KbdState,
|
||||
pub xkb_context: Context,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
|
|
@ -302,7 +312,7 @@ impl KeyboardState {
|
|||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_state: KbdState::new().unwrap(),
|
||||
xkb_context: Context::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
|
|
@ -385,16 +395,13 @@ fn key_input(
|
|||
let keyboard_state = seat_state.keyboard_state.as_mut().unwrap();
|
||||
|
||||
let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId));
|
||||
let event = keyboard_state
|
||||
.xkb_state
|
||||
.process_key_event(keycode, state, repeat);
|
||||
|
||||
event_sink.push_window_event(
|
||||
WindowEvent::KeyboardInput {
|
||||
if let Some(mut key_context) = keyboard_state.xkb_context.key_context() {
|
||||
let event = key_context.process_key_event(keycode, state, repeat);
|
||||
let event = WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
};
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::sync::{Arc, Mutex};
|
|||
|
||||
use x11_dl::xinput2::{
|
||||
self, XIDeviceEvent, XIEnterEvent, XIFocusInEvent, XIFocusOutEvent, XIHierarchyEvent,
|
||||
XILeaveEvent, XIRawEvent,
|
||||
XILeaveEvent, XIModifierState, XIRawEvent,
|
||||
};
|
||||
use x11_dl::xlib::{
|
||||
self, Display as XDisplay, Window as XWindow, XAnyEvent, XClientMessageEvent, XConfigureEvent,
|
||||
|
|
@ -14,9 +14,10 @@ use x11_dl::xlib::{
|
|||
XReparentEvent, XSelectionEvent, XVisibilityEvent, XkbAnyEvent,
|
||||
};
|
||||
use x11rb::protocol::xinput;
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _, ModMask};
|
||||
use x11rb::x11_utils::ExtensionInformation;
|
||||
use x11rb::x11_utils::Serialize;
|
||||
use xkbcommon_dl::xkb_mod_mask_t;
|
||||
|
||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
||||
use crate::event::{
|
||||
|
|
@ -26,7 +27,8 @@ use crate::event::{
|
|||
use crate::event::{InnerSizeWriter, MouseButton};
|
||||
use crate::event_loop::EventLoopWindowTarget as RootELW;
|
||||
use crate::keyboard::ModifiersState;
|
||||
use crate::platform_impl::platform::common::{keymap, xkb_state::KbdState};
|
||||
use crate::platform_impl::common::xkb::{self, XkbState};
|
||||
use crate::platform_impl::platform::common::xkb::Context;
|
||||
use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest};
|
||||
use crate::platform_impl::platform::x11::EventLoopWindowTarget;
|
||||
use crate::platform_impl::platform::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
|
||||
|
|
@ -47,7 +49,7 @@ pub struct EventProcessor {
|
|||
pub xi2ext: ExtensionInformation,
|
||||
pub xkbext: ExtensionInformation,
|
||||
pub target: RootELW,
|
||||
pub kb_state: KbdState,
|
||||
pub xkb_context: Context,
|
||||
// Number of touch events currently in progress
|
||||
pub num_touch: u32,
|
||||
// This is the last pressed key that is repeatable (if it hasn't been
|
||||
|
|
@ -178,10 +180,12 @@ impl EventProcessor {
|
|||
};
|
||||
|
||||
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
|
||||
self.xinput2_button_input(xev, state, &mut callback);
|
||||
}
|
||||
xinput2::XI_Motion => {
|
||||
let xev: &XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
||||
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
|
||||
self.xinput2_mouse_motion(xev, &mut callback);
|
||||
}
|
||||
xinput2::XI_Enter => {
|
||||
|
|
@ -190,6 +194,7 @@ impl EventProcessor {
|
|||
}
|
||||
xinput2::XI_Leave => {
|
||||
let xev: &XILeaveEvent = unsafe { &*(xev.data as *const _) };
|
||||
self.update_mods_from_xinput2_event(&xev.mods, &xev.group, &mut callback);
|
||||
self.xinput2_mouse_left(xev, &mut callback);
|
||||
}
|
||||
xinput2::XI_FocusIn => {
|
||||
|
|
@ -895,7 +900,12 @@ impl EventProcessor {
|
|||
// Only keys that can repeat should change the held_key_press state since a
|
||||
// continuously held repeatable key may continue repeating after the press of a
|
||||
// non-repeatable key.
|
||||
let repeat = if self.kb_state.key_repeats(keycode) {
|
||||
let key_repeats = self
|
||||
.xkb_context
|
||||
.keymap_mut()
|
||||
.map(|k| k.key_repeats(keycode))
|
||||
.unwrap_or(false);
|
||||
let repeat = if key_repeats {
|
||||
let is_latest_held = self.held_key_press == Some(keycode);
|
||||
|
||||
if state == ElementState::Pressed {
|
||||
|
|
@ -914,20 +924,29 @@ impl EventProcessor {
|
|||
false
|
||||
};
|
||||
|
||||
// Always update the modifiers.
|
||||
self.udpate_mods_from_core_event(xev.state as u16, &mut callback);
|
||||
|
||||
if keycode != 0 && !self.is_composing {
|
||||
let event = self.kb_state.process_key_event(keycode, state, repeat);
|
||||
callback(
|
||||
&self.target,
|
||||
Event::WindowEvent {
|
||||
if let Some(mut key_processor) = self.xkb_context.key_context() {
|
||||
let event = key_processor.process_key_event(keycode, state, repeat);
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
device_id,
|
||||
event,
|
||||
is_synthetic: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
} else if let Some(ic) = wt
|
||||
};
|
||||
callback(&self.target, event);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let wt = Self::window_target(&self.target);
|
||||
|
||||
if let Some(ic) = wt
|
||||
.ime
|
||||
.as_ref()
|
||||
.and_then(|ime| ime.borrow().get_context(window as XWindow))
|
||||
|
|
@ -1227,8 +1246,19 @@ impl EventProcessor {
|
|||
};
|
||||
callback(&self.target, event);
|
||||
|
||||
let modifiers: crate::keyboard::ModifiersState = self.kb_state.mods_state().into();
|
||||
self.send_modifiers(modifiers, &mut callback);
|
||||
// Issue key press events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
&self.target,
|
||||
window_id,
|
||||
ElementState::Pressed,
|
||||
&mut self.xkb_context,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
if let Some(state) = self.xkb_context.state_mut() {
|
||||
let mods = state.modifiers().into();
|
||||
self.send_modifiers(mods, &mut callback);
|
||||
}
|
||||
|
||||
// The deviceid for this event is for a keyboard instead of a pointer,
|
||||
// so we have to do a little extra work.
|
||||
|
|
@ -1247,15 +1277,6 @@ impl EventProcessor {
|
|||
},
|
||||
};
|
||||
callback(&self.target, event);
|
||||
|
||||
// Issue key press events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
&self.target,
|
||||
window_id,
|
||||
ElementState::Pressed,
|
||||
&mut self.kb_state,
|
||||
&mut callback,
|
||||
);
|
||||
}
|
||||
|
||||
fn xinput2_unfocused<T: 'static, F>(&mut self, xev: &XIFocusOutEvent, mut callback: F)
|
||||
|
|
@ -1283,12 +1304,14 @@ impl EventProcessor {
|
|||
|
||||
wt.update_listen_device_events(false);
|
||||
|
||||
self.send_modifiers(ModifiersState::empty(), &mut callback);
|
||||
|
||||
// Issue key release events for all pressed keys
|
||||
Self::handle_pressed_keys(
|
||||
&self.target,
|
||||
window_id,
|
||||
ElementState::Released,
|
||||
&mut self.kb_state,
|
||||
&mut self.xkb_context,
|
||||
&mut callback,
|
||||
);
|
||||
|
||||
|
|
@ -1296,8 +1319,6 @@ impl EventProcessor {
|
|||
// window regains focus.
|
||||
self.held_key_press = None;
|
||||
|
||||
self.send_modifiers(ModifiersState::empty(), &mut callback);
|
||||
|
||||
if let Some(window) = self.with_window(window, Arc::clone) {
|
||||
window.shared_state_lock().has_focus = false;
|
||||
}
|
||||
|
|
@ -1462,7 +1483,7 @@ impl EventProcessor {
|
|||
if keycode < KEYCODE_OFFSET as u32 {
|
||||
return;
|
||||
}
|
||||
let physical_key = keymap::raw_keycode_to_physicalkey(keycode);
|
||||
let physical_key = xkb::raw_keycode_to_physicalkey(keycode);
|
||||
|
||||
callback(
|
||||
&self.target,
|
||||
|
|
@ -1527,17 +1548,25 @@ impl EventProcessor {
|
|||
let keycodes_changed = util::has_flag(xev.changed, keycodes_changed_flag);
|
||||
let geometry_changed = util::has_flag(xev.changed, geometry_changed_flag);
|
||||
|
||||
if xev.device == self.kb_state.core_keyboard_id
|
||||
if xev.device == self.xkb_context.core_keyboard_id
|
||||
&& (keycodes_changed || geometry_changed)
|
||||
{
|
||||
unsafe { self.kb_state.init_with_x11_keymap() };
|
||||
let modifiers = self.kb_state.mods_state();
|
||||
self.send_modifiers(modifiers.into(), &mut callback);
|
||||
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
|
||||
self.xkb_context.set_keymap_from_x11(xcb);
|
||||
|
||||
if let Some(state) = self.xkb_context.state_mut() {
|
||||
let mods = state.modifiers().into();
|
||||
self.send_modifiers(mods, &mut callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
xlib::XkbMapNotify => {
|
||||
unsafe { self.kb_state.init_with_x11_keymap() };
|
||||
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
|
||||
let xcb = wt.xconn.xcb_connection().get_raw_xcb_connection();
|
||||
self.xkb_context.set_keymap_from_x11(xcb);
|
||||
if let Some(state) = self.xkb_context.state_mut() {
|
||||
let mods = state.modifiers().into();
|
||||
self.send_modifiers(mods, &mut callback);
|
||||
}
|
||||
}
|
||||
xlib::XkbStateNotify => {
|
||||
let xev = unsafe { &*(xev as *const _ as *const xlib::XkbStateNotifyEvent) };
|
||||
|
|
@ -1545,24 +1574,122 @@ impl EventProcessor {
|
|||
// Set the timestamp.
|
||||
wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
|
||||
|
||||
// NOTE: Modifiers could update without a prior event updating them,
|
||||
// thus diffing the state before and after is not reliable.
|
||||
|
||||
self.kb_state.update_modifiers(
|
||||
xev.base_mods,
|
||||
xev.latched_mods,
|
||||
xev.locked_mods,
|
||||
xev.base_group as u32,
|
||||
xev.latched_group as u32,
|
||||
xev.locked_group as u32,
|
||||
);
|
||||
|
||||
self.send_modifiers(self.kb_state.mods_state().into(), &mut callback);
|
||||
if let Some(state) = self.xkb_context.state_mut() {
|
||||
state.update_modifiers(
|
||||
xev.base_mods,
|
||||
xev.latched_mods,
|
||||
xev.locked_mods,
|
||||
xev.base_group as u32,
|
||||
xev.latched_group as u32,
|
||||
xev.locked_group as u32,
|
||||
);
|
||||
let mods = state.modifiers().into();
|
||||
self.send_modifiers(mods, &mut callback);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_mods_from_xinput2_event<T: 'static, F>(
|
||||
&mut self,
|
||||
mods: &XIModifierState,
|
||||
group: &XIModifierState,
|
||||
mut callback: F,
|
||||
) where
|
||||
F: FnMut(&RootELW, Event<T>),
|
||||
{
|
||||
if let Some(state) = self.xkb_context.state_mut() {
|
||||
state.update_modifiers(
|
||||
mods.base as u32,
|
||||
mods.latched as u32,
|
||||
mods.locked as u32,
|
||||
group.base as u32,
|
||||
group.latched as u32,
|
||||
group.locked as u32,
|
||||
);
|
||||
|
||||
let mods = state.modifiers();
|
||||
self.send_modifiers(mods.into(), &mut callback);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn udpate_mods_from_core_event<T: 'static, F>(&mut self, state: u16, mut callback: F)
|
||||
where
|
||||
F: FnMut(&RootELW, Event<T>),
|
||||
{
|
||||
let xkb_mask = self.xkb_mod_mask_from_core(state);
|
||||
let xkb_state = match self.xkb_context.state_mut() {
|
||||
Some(xkb_state) => xkb_state,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// NOTE: this is inspired by Qt impl.
|
||||
let mut depressed = xkb_state.depressed_modifiers() & xkb_mask;
|
||||
let latched = xkb_state.latched_modifiers() & xkb_mask;
|
||||
let locked = xkb_state.locked_modifiers() & xkb_mask;
|
||||
// Set modifiers in depressed if they don't appear in any of the final masks.
|
||||
depressed |= !(depressed | latched | locked) & xkb_mask;
|
||||
|
||||
xkb_state.update_modifiers(
|
||||
depressed,
|
||||
latched,
|
||||
locked,
|
||||
0,
|
||||
0,
|
||||
// Bits 13 and 14 report the state keyboard group.
|
||||
((state >> 13) & 3) as u32,
|
||||
);
|
||||
|
||||
let mods = xkb_state.modifiers();
|
||||
self.send_modifiers(mods.into(), &mut callback);
|
||||
}
|
||||
|
||||
pub fn xkb_mod_mask_from_core(&mut self, state: u16) -> xkb_mod_mask_t {
|
||||
let mods_indices = match self.xkb_context.keymap_mut() {
|
||||
Some(keymap) => keymap.mods_indices(),
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
// Build the XKB modifiers from the regular state.
|
||||
let mut depressed = 0u32;
|
||||
if let Some(shift) = mods_indices
|
||||
.shift
|
||||
.filter(|_| ModMask::SHIFT.intersects(state))
|
||||
{
|
||||
depressed |= 1 << shift;
|
||||
}
|
||||
if let Some(caps) = mods_indices
|
||||
.caps
|
||||
.filter(|_| ModMask::LOCK.intersects(state))
|
||||
{
|
||||
depressed |= 1 << caps;
|
||||
}
|
||||
if let Some(ctrl) = mods_indices
|
||||
.ctrl
|
||||
.filter(|_| ModMask::CONTROL.intersects(state))
|
||||
{
|
||||
depressed |= 1 << ctrl;
|
||||
}
|
||||
if let Some(alt) = mods_indices.alt.filter(|_| ModMask::M1.intersects(state)) {
|
||||
depressed |= 1 << alt;
|
||||
}
|
||||
if let Some(num) = mods_indices.num.filter(|_| ModMask::M2.intersects(state)) {
|
||||
depressed |= 1 << num;
|
||||
}
|
||||
if let Some(mod3) = mods_indices.mod3.filter(|_| ModMask::M3.intersects(state)) {
|
||||
depressed |= 1 << mod3;
|
||||
}
|
||||
if let Some(logo) = mods_indices.logo.filter(|_| ModMask::M4.intersects(state)) {
|
||||
depressed |= 1 << logo;
|
||||
}
|
||||
if let Some(mod5) = mods_indices.mod5.filter(|_| ModMask::M5.intersects(state)) {
|
||||
depressed |= 1 << mod5;
|
||||
}
|
||||
|
||||
depressed
|
||||
}
|
||||
|
||||
/// Send modifiers for the active window.
|
||||
///
|
||||
/// The event won't be send when the `modifiers` match the previosly `sent` modifiers value.
|
||||
|
|
@ -1591,7 +1718,7 @@ impl EventProcessor {
|
|||
target: &RootELW,
|
||||
window_id: crate::window::WindowId,
|
||||
state: ElementState,
|
||||
kb_state: &mut KbdState,
|
||||
xkb_context: &mut Context,
|
||||
callback: &mut F,
|
||||
) where
|
||||
F: FnMut(&RootELW, Event<T>),
|
||||
|
|
@ -1600,14 +1727,33 @@ impl EventProcessor {
|
|||
|
||||
// Update modifiers state and emit key events based on which keys are currently pressed.
|
||||
let window_target = Self::window_target(target);
|
||||
let xcb = window_target
|
||||
.xconn
|
||||
.xcb_connection()
|
||||
.get_raw_xcb_connection();
|
||||
|
||||
let keymap = match xkb_context.keymap_mut() {
|
||||
Some(keymap) => keymap,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Send the keys using the sythetic state to not alter the main state.
|
||||
let mut xkb_state = match XkbState::new_x11(xcb, keymap) {
|
||||
Some(xkb_state) => xkb_state,
|
||||
None => return,
|
||||
};
|
||||
let mut key_processor = match xkb_context.key_context_with_state(&mut xkb_state) {
|
||||
Some(key_processor) => key_processor,
|
||||
None => return,
|
||||
};
|
||||
|
||||
for keycode in window_target
|
||||
.xconn
|
||||
.query_keymap()
|
||||
.into_iter()
|
||||
.filter(|k| *k >= KEYCODE_OFFSET)
|
||||
{
|
||||
let keycode = keycode as u32;
|
||||
let event = kb_state.process_key_event(keycode, state, false);
|
||||
let event = key_processor.process_key_event(keycode as u32, state, false);
|
||||
let event = Event::WindowEvent {
|
||||
window_id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
|||
use x11rb::x11_utils::X11Error as LogicalError;
|
||||
use x11rb::xcb_ffi::ReplyOrIdError;
|
||||
|
||||
use super::{common::xkb_state::KbdState, ControlFlow, OsError};
|
||||
use super::{ControlFlow, OsError};
|
||||
use crate::{
|
||||
error::{EventLoopError, OsError as RootOsError},
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{DeviceEvents, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||
platform::pump_events::PumpStatus,
|
||||
platform_impl::common::xkb::Context,
|
||||
platform_impl::platform::{min_timeout, WindowId},
|
||||
window::WindowAttributes,
|
||||
};
|
||||
|
|
@ -281,8 +282,8 @@ impl<T: 'static> EventLoop<T> {
|
|||
// Create a channel for sending user events.
|
||||
let (user_sender, user_channel) = mpsc::channel();
|
||||
|
||||
let kb_state =
|
||||
KbdState::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
let xkb_context =
|
||||
Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap();
|
||||
|
||||
let window_target = EventLoopWindowTarget {
|
||||
ime,
|
||||
|
|
@ -322,7 +323,7 @@ impl<T: 'static> EventLoop<T> {
|
|||
ime_event_receiver,
|
||||
xi2ext,
|
||||
xkbext,
|
||||
kb_state,
|
||||
xkb_context,
|
||||
num_touch: 0,
|
||||
held_key_press: None,
|
||||
first_touch: None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue