Keyboard input, text input elements
This commit is contained in:
@@ -4,12 +4,13 @@ use std::error::Error;
|
||||
use std::ffi::c_void;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::num::NonZeroU32;
|
||||
use std::os::fd::{AsFd, AsRawFd};
|
||||
use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use raw_window_handle::{
|
||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
|
||||
@@ -18,15 +19,17 @@ use raw_window_handle::{
|
||||
use ruin_runtime::channel::mpsc;
|
||||
use ruin_runtime::{WorkerHandle, queue_future, queue_task, spawn_worker};
|
||||
use ruin_ui::{
|
||||
CursorIcon, PlatformEndpoint, PlatformEvent, PlatformRequest, PlatformRuntime, Point,
|
||||
PointerButton, PointerEvent, PointerEventKind, SceneSnapshot, UiRuntime, UiSize,
|
||||
WindowConfigured, WindowId, WindowLifecycle, WindowSpec, WindowUpdate,
|
||||
CursorIcon, KeyboardEvent, KeyboardEventKind, KeyboardKey, KeyboardModifiers, PlatformEndpoint,
|
||||
PlatformEvent, PlatformRequest, PlatformRuntime, Point, PointerButton, PointerEvent,
|
||||
PointerEventKind, SceneSnapshot, UiRuntime, UiSize, WindowConfigured, WindowId,
|
||||
WindowLifecycle, WindowSpec, WindowUpdate,
|
||||
};
|
||||
use ruin_ui_renderer_wgpu::{RenderError, WgpuSceneRenderer};
|
||||
use tracing::Level;
|
||||
use tracing::{debug, trace};
|
||||
use wayland_client::globals::{GlobalListContents, registry_queue_init};
|
||||
use wayland_client::protocol::{
|
||||
wl_callback, wl_compositor, wl_pointer, wl_registry, wl_seat, wl_surface,
|
||||
wl_callback, wl_compositor, wl_keyboard, wl_pointer, wl_registry, wl_seat, wl_surface,
|
||||
};
|
||||
use wayland_client::{
|
||||
Connection, Dispatch, Proxy, QueueHandle, WEnum, delegate_noop, event_created_child,
|
||||
@@ -39,6 +42,7 @@ use wayland_protocols::wp::primary_selection::zv1::client::{
|
||||
zwp_primary_selection_offer_v1, zwp_primary_selection_source_v1,
|
||||
};
|
||||
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
|
||||
use xkbcommon::xkb;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WaylandSurfaceTarget {
|
||||
@@ -114,6 +118,7 @@ struct WindowWorkerState {
|
||||
enum WindowWorkerCommand {
|
||||
ReplaceScene(SceneSnapshot),
|
||||
SetPrimarySelectionText(String),
|
||||
RequestPrimarySelectionText,
|
||||
SetCursorIcon(CursorIcon),
|
||||
ApplySpec(WindowSpec),
|
||||
Shutdown,
|
||||
@@ -139,6 +144,11 @@ struct State {
|
||||
primary_selection_device: Option<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1>,
|
||||
qh: QueueHandle<State>,
|
||||
pointer: Option<wl_pointer::WlPointer>,
|
||||
keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
keyboard_focused: bool,
|
||||
xkb_context: xkb::Context,
|
||||
xkb_keymap: Option<xkb::Keymap>,
|
||||
xkb_state: Option<xkb::State>,
|
||||
current_size: (u32, u32),
|
||||
configured: bool,
|
||||
pending_size: Option<(u32, u32)>,
|
||||
@@ -146,17 +156,54 @@ struct State {
|
||||
frame_callback: Option<wl_callback::WlCallback>,
|
||||
pointer_position: Option<Point>,
|
||||
pending_pointer_events: Vec<PointerEvent>,
|
||||
pending_keyboard_events: Vec<KeyboardEvent>,
|
||||
keyboard_modifiers: KeyboardModifiers,
|
||||
keyboard_repeat_rate: i32,
|
||||
keyboard_repeat_delay: Duration,
|
||||
keyboard_repeat: Option<KeyboardRepeatState>,
|
||||
primary_selection_source: Option<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1>,
|
||||
primary_selection_text: Option<String>,
|
||||
primary_selection_offer: Option<zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1>,
|
||||
primary_selection_offer_mime_types: Vec<String>,
|
||||
last_selection_serial: Option<u32>,
|
||||
last_pointer_enter_serial: Option<u32>,
|
||||
cursor_icon: CursorIcon,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct KeyboardRepeatState {
|
||||
keycode: u32,
|
||||
next_at: Instant,
|
||||
interval: Duration,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn request_redraw(&mut self) {
|
||||
self.needs_redraw = true;
|
||||
}
|
||||
|
||||
fn queue_ready_keyboard_repeats(&mut self) {
|
||||
let Some(repeat) = self.keyboard_repeat.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let Some(xkb_state) = self.xkb_state.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let now = Instant::now();
|
||||
while now >= repeat.next_at {
|
||||
let keycode = xkb::Keycode::new(repeat.keycode + 8);
|
||||
let text = keyboard_text_for_xkb(xkb_state, keycode);
|
||||
let key = keyboard_key_from_xkb(xkb_state.key_get_one_sym(keycode), text.as_deref());
|
||||
self.pending_keyboard_events.push(KeyboardEvent::new(
|
||||
repeat.keycode,
|
||||
KeyboardEventKind::Pressed,
|
||||
key,
|
||||
self.keyboard_modifiers,
|
||||
text,
|
||||
));
|
||||
repeat.next_at += repeat.interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wayland_cursor_shape(icon: CursorIcon) -> wp_cursor_shape_device_v1::Shape {
|
||||
@@ -178,6 +225,48 @@ fn apply_cursor_icon(state: &mut State) {
|
||||
let _ = state._connection.flush();
|
||||
}
|
||||
|
||||
fn keyboard_modifiers_from_xkb(state: &xkb::State) -> KeyboardModifiers {
|
||||
KeyboardModifiers {
|
||||
shift: state.mod_name_is_active(xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE),
|
||||
control: state.mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE),
|
||||
alt: state.mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE),
|
||||
super_key: state.mod_name_is_active(xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE),
|
||||
}
|
||||
}
|
||||
|
||||
fn keyboard_text_for_xkb(state: &xkb::State, keycode: xkb::Keycode) -> Option<String> {
|
||||
let text = state.key_get_utf8(keycode);
|
||||
if text.is_empty() || text.chars().any(char::is_control) {
|
||||
return None;
|
||||
}
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn keyboard_key_from_xkb(keysym: xkb::Keysym, text: Option<&str>) -> KeyboardKey {
|
||||
match xkb::keysym_get_name(keysym).as_str() {
|
||||
"BackSpace" => KeyboardKey::Backspace,
|
||||
"Delete" => KeyboardKey::Delete,
|
||||
"Return" => KeyboardKey::Enter,
|
||||
"Tab" => KeyboardKey::Tab,
|
||||
"Escape" => KeyboardKey::Escape,
|
||||
"Left" => KeyboardKey::ArrowLeft,
|
||||
"Right" => KeyboardKey::ArrowRight,
|
||||
"Up" => KeyboardKey::ArrowUp,
|
||||
"Down" => KeyboardKey::ArrowDown,
|
||||
"Home" => KeyboardKey::Home,
|
||||
"End" => KeyboardKey::End,
|
||||
_ => text
|
||||
.filter(|text| !text.is_empty())
|
||||
.map(str::to_owned)
|
||||
.or_else(|| {
|
||||
let utf8 = xkb::keysym_to_utf8(keysym);
|
||||
(!utf8.is_empty() && !utf8.chars().any(char::is_control)).then_some(utf8)
|
||||
})
|
||||
.map(KeyboardKey::Character)
|
||||
.unwrap_or(KeyboardKey::Unknown),
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandWindow {
|
||||
pub fn open(spec: WindowSpec) -> Result<Self, Box<dyn Error>> {
|
||||
let connection = Connection::connect_to_env()?;
|
||||
@@ -241,6 +330,11 @@ impl WaylandWindow {
|
||||
primary_selection_device,
|
||||
qh,
|
||||
pointer: None,
|
||||
keyboard: None,
|
||||
keyboard_focused: false,
|
||||
xkb_context: xkb::Context::new(xkb::CONTEXT_NO_FLAGS),
|
||||
xkb_keymap: None,
|
||||
xkb_state: None,
|
||||
current_size: (initial_width, initial_height),
|
||||
configured: false,
|
||||
pending_size: None,
|
||||
@@ -248,8 +342,15 @@ impl WaylandWindow {
|
||||
frame_callback: None,
|
||||
pointer_position: None,
|
||||
pending_pointer_events: Vec::new(),
|
||||
pending_keyboard_events: Vec::new(),
|
||||
keyboard_modifiers: KeyboardModifiers::default(),
|
||||
keyboard_repeat_rate: 25,
|
||||
keyboard_repeat_delay: Duration::from_millis(500),
|
||||
keyboard_repeat: None,
|
||||
primary_selection_source: None,
|
||||
primary_selection_text: None,
|
||||
primary_selection_offer: None,
|
||||
primary_selection_offer_mime_types: Vec::new(),
|
||||
last_selection_serial: None,
|
||||
last_pointer_enter_serial: None,
|
||||
cursor_icon: CursorIcon::Default,
|
||||
@@ -430,6 +531,43 @@ impl WaylandWindow {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_primary_selection_text(&mut self) -> Result<Option<String>, Box<dyn Error>> {
|
||||
let preferred_mime = self
|
||||
.state
|
||||
.primary_selection_offer_mime_types
|
||||
.iter()
|
||||
.find(|mime| mime.as_str() == "text/plain;charset=utf-8")
|
||||
.or_else(|| {
|
||||
self.state
|
||||
.primary_selection_offer_mime_types
|
||||
.iter()
|
||||
.find(|mime| mime.as_str() == "text/plain")
|
||||
})
|
||||
.cloned();
|
||||
let Some(mime_type) = preferred_mime else {
|
||||
return Ok(self.state.primary_selection_text.clone());
|
||||
};
|
||||
let Some(offer) = self.state.primary_selection_offer.as_ref() else {
|
||||
return Ok(self.state.primary_selection_text.clone());
|
||||
};
|
||||
|
||||
let mut pipe_fds = [0; 2];
|
||||
let pipe_result = unsafe { libc::pipe2(pipe_fds.as_mut_ptr(), libc::O_CLOEXEC) };
|
||||
if pipe_result != 0 {
|
||||
return Err(Box::new(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
let read_fd = unsafe { OwnedFd::from_raw_fd(pipe_fds[0]) };
|
||||
let write_fd = unsafe { OwnedFd::from_raw_fd(pipe_fds[1]) };
|
||||
offer.receive(mime_type, write_fd.as_fd());
|
||||
self.state._connection.flush()?;
|
||||
drop(write_fd);
|
||||
let mut file = File::from(read_fd);
|
||||
let mut text = String::new();
|
||||
file.read_to_string(&mut text)?;
|
||||
Ok((!text.is_empty()).then_some(text))
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&mut self, cursor: CursorIcon) -> Result<(), Box<dyn Error>> {
|
||||
self.state.cursor_icon = cursor;
|
||||
apply_cursor_icon(&mut self.state);
|
||||
@@ -476,6 +614,11 @@ impl WaylandWindow {
|
||||
std::mem::take(&mut self.state.pending_pointer_events)
|
||||
}
|
||||
|
||||
pub fn drain_keyboard_events(&mut self) -> Vec<KeyboardEvent> {
|
||||
self.state.queue_ready_keyboard_repeats();
|
||||
std::mem::take(&mut self.state.pending_keyboard_events)
|
||||
}
|
||||
|
||||
pub fn prepare_frame(&mut self) -> Option<FrameRequest> {
|
||||
if !self.state.configured {
|
||||
return None;
|
||||
@@ -537,6 +680,9 @@ fn run_wayland_platform(mut endpoint: PlatformEndpoint) {
|
||||
PlatformRequest::SetPrimarySelectionText { window_id, text } => {
|
||||
handle_set_primary_selection_text(&state, window_id, text);
|
||||
}
|
||||
PlatformRequest::RequestPrimarySelectionText { window_id } => {
|
||||
handle_request_primary_selection_text(&state, window_id);
|
||||
}
|
||||
PlatformRequest::SetCursorIcon { window_id, cursor } => {
|
||||
handle_set_cursor_icon(&state, window_id, cursor);
|
||||
}
|
||||
@@ -546,6 +692,12 @@ fn run_wayland_platform(mut endpoint: PlatformEndpoint) {
|
||||
PlatformRequest::EmitPointerEvent { window_id, event } => {
|
||||
emit_wayland_event(&state, PlatformEvent::Pointer { window_id, event });
|
||||
}
|
||||
PlatformRequest::EmitKeyboardEvent { window_id, event } => {
|
||||
emit_wayland_event(&state, PlatformEvent::Keyboard { window_id, event });
|
||||
}
|
||||
PlatformRequest::EmitWake { window_id, token } => {
|
||||
emit_wayland_event(&state, PlatformEvent::Wake { window_id, token });
|
||||
}
|
||||
PlatformRequest::Shutdown => {
|
||||
shutdown_wayland_backend(&state);
|
||||
break;
|
||||
@@ -702,6 +854,21 @@ fn handle_set_primary_selection_text(
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request_primary_selection_text(
|
||||
state: &Rc<RefCell<WaylandBackendState>>,
|
||||
window_id: WindowId,
|
||||
) {
|
||||
if let Some(command_tx) = state
|
||||
.borrow()
|
||||
.windows
|
||||
.get(&window_id)
|
||||
.and_then(|record| record.worker.as_ref())
|
||||
.map(|worker| worker.command_tx.clone())
|
||||
{
|
||||
let _ = command_tx.send(WindowWorkerCommand::RequestPrimarySelectionText);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_set_cursor_icon(
|
||||
state: &Rc<RefCell<WaylandBackendState>>,
|
||||
window_id: WindowId,
|
||||
@@ -818,6 +985,28 @@ fn spawn_window_worker(
|
||||
);
|
||||
}
|
||||
}
|
||||
WindowWorkerCommand::RequestPrimarySelectionText => {
|
||||
let mut state_ref = state.borrow_mut();
|
||||
match state_ref.window.read_primary_selection_text() {
|
||||
Ok(Some(text)) => {
|
||||
let _ = state_ref.event_tx.send(
|
||||
PlatformEvent::PrimarySelectionText {
|
||||
window_id: state_ref.window_id,
|
||||
text,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
debug!(
|
||||
target: "ruin_ui_platform_wayland::clipboard",
|
||||
window_id = state_ref.window_id.raw(),
|
||||
error = %error,
|
||||
"failed to read primary selection text"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowWorkerCommand::SetCursorIcon(cursor) => {
|
||||
let mut state_ref = state.borrow_mut();
|
||||
if let Err(error) = state_ref.window.set_cursor_icon(cursor) {
|
||||
@@ -877,6 +1066,21 @@ fn pump_window_worker(state: Rc<RefCell<WindowWorkerState>>) {
|
||||
event,
|
||||
});
|
||||
}
|
||||
for event in state_ref.window.drain_keyboard_events() {
|
||||
tracing::trace!(
|
||||
target: "ruin_ui_platform_wayland::event_bridge",
|
||||
window_id = state_ref.window_id.raw(),
|
||||
keycode = event.keycode,
|
||||
?event.kind,
|
||||
?event.key,
|
||||
text = event.text.as_deref().unwrap_or(""),
|
||||
"forwarding keyboard event to UI runtime"
|
||||
);
|
||||
let _ = state_ref.event_tx.send(PlatformEvent::Keyboard {
|
||||
window_id: state_ref.window_id,
|
||||
event,
|
||||
});
|
||||
}
|
||||
|
||||
if !state_ref.window.is_running() {
|
||||
emit_window_closed(&mut state_ref, true);
|
||||
@@ -1086,8 +1290,6 @@ delegate_noop!(
|
||||
State: ignore
|
||||
zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1
|
||||
);
|
||||
delegate_noop!(State: ignore zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1);
|
||||
|
||||
impl Dispatch<wl_seat::WlSeat, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
@@ -1116,6 +1318,17 @@ impl Dispatch<wl_seat::WlSeat, ()> for State {
|
||||
state.pointer_position = None;
|
||||
state.last_pointer_enter_serial = None;
|
||||
}
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
if state.keyboard.is_none() {
|
||||
state.keyboard = Some(seat.get_keyboard(qh, ()));
|
||||
}
|
||||
} else {
|
||||
state.keyboard = None;
|
||||
state.xkb_keymap = None;
|
||||
state.xkb_state = None;
|
||||
state.keyboard_modifiers = KeyboardModifiers::default();
|
||||
state.keyboard_repeat = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1178,17 +1391,21 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
let Some(position) = state.pointer_position else {
|
||||
return;
|
||||
};
|
||||
if button != 0x110 {
|
||||
return;
|
||||
let button = match button {
|
||||
0x110 => PointerButton::Primary,
|
||||
0x112 => PointerButton::Middle,
|
||||
_ => return,
|
||||
};
|
||||
if button == PointerButton::Primary {
|
||||
state.last_selection_serial = Some(serial);
|
||||
}
|
||||
state.last_selection_serial = Some(serial);
|
||||
let kind = match button_state {
|
||||
WEnum::Value(wl_pointer::ButtonState::Pressed) => PointerEventKind::Down {
|
||||
button: PointerButton::Primary,
|
||||
},
|
||||
WEnum::Value(wl_pointer::ButtonState::Released) => PointerEventKind::Up {
|
||||
button: PointerButton::Primary,
|
||||
},
|
||||
WEnum::Value(wl_pointer::ButtonState::Pressed) => {
|
||||
PointerEventKind::Down { button }
|
||||
}
|
||||
WEnum::Value(wl_pointer::ButtonState::Released) => {
|
||||
PointerEventKind::Up { button }
|
||||
}
|
||||
WEnum::Value(_) | WEnum::Unknown(_) => return,
|
||||
};
|
||||
state
|
||||
@@ -1200,15 +1417,200 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, ()> for State {
|
||||
impl Dispatch<wl_keyboard::WlKeyboard, ()> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_data_device: &zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1,
|
||||
_event: zwp_primary_selection_device_v1::Event,
|
||||
state: &mut Self,
|
||||
_keyboard: &wl_keyboard::WlKeyboard,
|
||||
event: wl_keyboard::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
tracing::event!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
Level::INFO,
|
||||
?event,
|
||||
"received keyboard event"
|
||||
);
|
||||
match event {
|
||||
wl_keyboard::Event::Keymap { format, fd, size } => {
|
||||
let WEnum::Value(wl_keyboard::KeymapFormat::XkbV1) = format else {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
?format,
|
||||
"ignored unsupported keymap format"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let Ok(size) = usize::try_from(size) else {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
size,
|
||||
"ignored keymap with invalid size"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let keymap = match unsafe {
|
||||
xkb::Keymap::new_from_fd(
|
||||
&state.xkb_context,
|
||||
fd,
|
||||
size,
|
||||
xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
} {
|
||||
Ok(Some(keymap)) => keymap,
|
||||
Ok(None) => {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
"failed to compile XKB keymap"
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
size,
|
||||
error = %error,
|
||||
"failed to map compositor keymap fd"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
state.xkb_state = Some(xkb::State::new(&keymap));
|
||||
state.xkb_keymap = Some(keymap);
|
||||
state.keyboard_modifiers = KeyboardModifiers::default();
|
||||
state.keyboard_repeat = None;
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
"installed XKB keymap"
|
||||
);
|
||||
}
|
||||
wl_keyboard::Event::Enter { .. } => {
|
||||
state.keyboard_focused = true;
|
||||
state.keyboard_repeat = None;
|
||||
}
|
||||
wl_keyboard::Event::Leave { .. } => {
|
||||
state.keyboard_focused = false;
|
||||
state.keyboard_modifiers = KeyboardModifiers::default();
|
||||
state.keyboard_repeat = None;
|
||||
}
|
||||
wl_keyboard::Event::RepeatInfo { rate, delay } => {
|
||||
state.keyboard_repeat_rate = rate;
|
||||
state.keyboard_repeat_delay = Duration::from_millis(delay.max(0) as u64);
|
||||
if rate <= 0 {
|
||||
state.keyboard_repeat = None;
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
..
|
||||
} => {
|
||||
let Some(xkb_state) = state.xkb_state.as_mut() else {
|
||||
return;
|
||||
};
|
||||
xkb_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||
state.keyboard_modifiers = keyboard_modifiers_from_xkb(xkb_state);
|
||||
}
|
||||
wl_keyboard::Event::Key {
|
||||
key,
|
||||
state: key_state,
|
||||
..
|
||||
} => {
|
||||
if !state.keyboard_focused {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
key,
|
||||
?key_state,
|
||||
"dropping key because keyboard focus is not active"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let Some(xkb_state) = state.xkb_state.as_mut() else {
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
key,
|
||||
?key_state,
|
||||
"dropping key because XKB state is not initialized"
|
||||
);
|
||||
return;
|
||||
};
|
||||
let (kind, direction) = match key_state {
|
||||
WEnum::Value(wl_keyboard::KeyState::Pressed) => {
|
||||
(KeyboardEventKind::Pressed, xkb::KeyDirection::Down)
|
||||
}
|
||||
WEnum::Value(wl_keyboard::KeyState::Released) => {
|
||||
(KeyboardEventKind::Released, xkb::KeyDirection::Up)
|
||||
}
|
||||
WEnum::Value(_) | WEnum::Unknown(_) => return,
|
||||
};
|
||||
let keycode = xkb::Keycode::new(key + 8);
|
||||
xkb_state.update_key(keycode, direction);
|
||||
state.keyboard_modifiers = keyboard_modifiers_from_xkb(xkb_state);
|
||||
let text = matches!(kind, KeyboardEventKind::Pressed)
|
||||
.then(|| keyboard_text_for_xkb(xkb_state, keycode))
|
||||
.flatten();
|
||||
let logical_key =
|
||||
keyboard_key_from_xkb(xkb_state.key_get_one_sym(keycode), text.as_deref());
|
||||
state.pending_keyboard_events.push(KeyboardEvent::new(
|
||||
key,
|
||||
kind,
|
||||
logical_key.clone(),
|
||||
state.keyboard_modifiers,
|
||||
text,
|
||||
));
|
||||
tracing::info!(
|
||||
target: "ruin_ui_platform_wayland::keyboard",
|
||||
keycode = key,
|
||||
?kind,
|
||||
?logical_key,
|
||||
modifiers = ?state.keyboard_modifiers,
|
||||
queued = state.pending_keyboard_events.len(),
|
||||
"queued translated keyboard event"
|
||||
);
|
||||
if kind == KeyboardEventKind::Pressed
|
||||
&& state.keyboard_repeat_rate > 0
|
||||
&& state
|
||||
.xkb_keymap
|
||||
.as_ref()
|
||||
.is_some_and(|keymap| keymap.key_repeats(keycode))
|
||||
{
|
||||
state.keyboard_repeat = Some(KeyboardRepeatState {
|
||||
keycode: key,
|
||||
next_at: Instant::now() + state.keyboard_repeat_delay,
|
||||
interval: Duration::from_secs_f64(
|
||||
1.0 / f64::from(state.keyboard_repeat_rate),
|
||||
),
|
||||
});
|
||||
} else if state
|
||||
.keyboard_repeat
|
||||
.as_ref()
|
||||
.is_some_and(|repeat| repeat.keycode == key)
|
||||
{
|
||||
state.keyboard_repeat = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_data_device: &zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1,
|
||||
event: zwp_primary_selection_device_v1::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let zwp_primary_selection_device_v1::Event::Selection { id } = event {
|
||||
state.primary_selection_offer = id;
|
||||
state.primary_selection_offer_mime_types.clear();
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State, zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, [
|
||||
@@ -1217,6 +1619,23 @@ impl Dispatch<zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, ()>
|
||||
]);
|
||||
}
|
||||
|
||||
impl Dispatch<zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
offer: &zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1,
|
||||
event: zwp_primary_selection_offer_v1::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let zwp_primary_selection_offer_v1::Event::Offer { mime_type } = event
|
||||
&& state.primary_selection_offer.as_ref() == Some(offer)
|
||||
{
|
||||
state.primary_selection_offer_mime_types.push(mime_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
|
||||
Reference in New Issue
Block a user