mirror of
https://github.com/rust-windowing/winit.git
synced 2026-09-02 23:00:05 -04:00
Move Wayland backend to winit-wayland (#4252)
This commit is contained in:
377
winit-wayland/src/seat/keyboard/mod.rs
Normal file
377
winit-wayland/src/seat/keyboard/mod.rs
Normal file
@@ -0,0 +1,377 @@
|
||||
//! The keyboard input handling.
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
use calloop::{LoopHandle, RegistrationToken};
|
||||
use sctk::reexports::client::protocol::wl_keyboard::{
|
||||
Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, WlKeyboard,
|
||||
};
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum};
|
||||
use tracing::warn;
|
||||
use winit_common::xkb::Context;
|
||||
use winit_core::event::{ElementState, WindowEvent};
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
|
||||
use crate::event_loop::sink::EventSink;
|
||||
use crate::state::WinitState;
|
||||
use crate::WindowId;
|
||||
|
||||
impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
wl_keyboard: &WlKeyboard,
|
||||
event: <WlKeyboard as Proxy>::Event,
|
||||
data: &KeyboardData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let seat_state = match state.seats.get_mut(&data.seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received keyboard event {event:?} without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let keyboard_state = match seat_state.keyboard_state.as_mut() {
|
||||
Some(keyboard_state) => keyboard_state,
|
||||
None => {
|
||||
warn!("Received keyboard event {event:?} without keyboard");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
match event {
|
||||
WlKeyboardEvent::Keymap { format, fd, size } => match format {
|
||||
WEnum::Value(format) => match format {
|
||||
WlKeymapFormat::NoKeymap => {
|
||||
warn!("non-xkb compatible keymap")
|
||||
},
|
||||
WlKeymapFormat::XkbV1 => {
|
||||
let context = &mut keyboard_state.xkb_context;
|
||||
context.set_keymap_from_fd(fd, size as usize);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
WEnum::Unknown(value) => {
|
||||
warn!("unknown keymap format 0x{:x}", value)
|
||||
},
|
||||
},
|
||||
WlKeyboardEvent::Enter { surface, .. } => {
|
||||
let window_id = crate::make_wid(&surface);
|
||||
|
||||
// Mark the window as focused.
|
||||
let was_unfocused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
let was_unfocused = !window.has_focus();
|
||||
window.add_seat_focus(data.seat.id());
|
||||
was_unfocused
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Drop the repeat, if there were any.
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
*data.window_id.lock().unwrap() = Some(window_id);
|
||||
|
||||
// The keyboard focus is considered as general focus.
|
||||
if was_unfocused {
|
||||
state.events_sink.push_window_event(WindowEvent::Focused(true), window_id);
|
||||
}
|
||||
|
||||
// HACK: this is just for GNOME not fixing their ordering issue of modifiers.
|
||||
if std::mem::take(&mut seat_state.modifiers_pending) {
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(seat_state.modifiers.into()),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
WlKeyboardEvent::Leave { surface, .. } => {
|
||||
let window_id = crate::make_wid(&surface);
|
||||
|
||||
// NOTE: we should drop the repeat regardless whethere it was for the present
|
||||
// window of for the window which just went gone.
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
// NOTE: The check whether the window exists is essential as we might get a
|
||||
// nil surface, regardless of what protocol says.
|
||||
let focused = match state.windows.get_mut().get(&window_id) {
|
||||
Some(window) => {
|
||||
let mut window = window.lock().unwrap();
|
||||
window.remove_seat_focus(&data.seat.id());
|
||||
window.has_focus()
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
|
||||
// We don't need to update it above, because the next `Enter` will overwrite
|
||||
// anyway.
|
||||
*data.window_id.lock().unwrap() = None;
|
||||
|
||||
if !focused {
|
||||
// Notify that no modifiers are being pressed.
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(ModifiersState::empty().into()),
|
||||
window_id,
|
||||
);
|
||||
|
||||
state.events_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
},
|
||||
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Pressed), .. } => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
ElementState::Pressed,
|
||||
false,
|
||||
);
|
||||
|
||||
let delay = match keyboard_state.repeat_info {
|
||||
RepeatInfo::Repeat { delay, .. } => delay,
|
||||
RepeatInfo::Disable => return,
|
||||
};
|
||||
|
||||
if !keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboard_state.current_repeat = Some(key);
|
||||
|
||||
// NOTE terminate ongoing timer and start a new timer.
|
||||
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let timer = Timer::from_duration(delay);
|
||||
let wl_keyboard = wl_keyboard.clone();
|
||||
keyboard_state.repeat_token = keyboard_state
|
||||
.loop_handle
|
||||
.insert_source(timer, move |_, _, state| {
|
||||
// Required to handle the wakeups from the repeat sources.
|
||||
state.dispatched_events = true;
|
||||
|
||||
let data = wl_keyboard.data::<KeyboardData>().unwrap();
|
||||
let seat_state = match state.seats.get_mut(&data.seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => return TimeoutAction::Drop,
|
||||
};
|
||||
|
||||
let keyboard_state = match seat_state.keyboard_state.as_mut() {
|
||||
Some(keyboard_state) => keyboard_state,
|
||||
None => return TimeoutAction::Drop,
|
||||
};
|
||||
|
||||
// NOTE: The removed on event source is batched, but key change to `None`
|
||||
// is instant.
|
||||
let repeat_keycode = match keyboard_state.current_repeat {
|
||||
Some(repeat_keycode) => repeat_keycode,
|
||||
None => return TimeoutAction::Drop,
|
||||
};
|
||||
|
||||
key_input(
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
repeat_keycode,
|
||||
ElementState::Pressed,
|
||||
true,
|
||||
);
|
||||
|
||||
// NOTE: the gap could change dynamically while repeat is going.
|
||||
match keyboard_state.repeat_info {
|
||||
RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap),
|
||||
RepeatInfo::Disable => TimeoutAction::Drop,
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => {
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
keyboard_state,
|
||||
&mut state.events_sink,
|
||||
data,
|
||||
key,
|
||||
ElementState::Released,
|
||||
false,
|
||||
);
|
||||
|
||||
if keyboard_state.repeat_info != RepeatInfo::Disable
|
||||
&& keyboard_state.xkb_context.keymap_mut().unwrap().key_repeats(key)
|
||||
&& Some(key) == keyboard_state.current_repeat
|
||||
{
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(token);
|
||||
}
|
||||
}
|
||||
},
|
||||
WlKeyboardEvent::Modifiers {
|
||||
mods_depressed, mods_latched, mods_locked, group, ..
|
||||
} => {
|
||||
let xkb_context = &mut keyboard_state.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.modifiers().into();
|
||||
|
||||
// HACK: part of the workaround from `WlKeyboardEvent::Enter`.
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
Some(window_id) => window_id,
|
||||
None => {
|
||||
seat_state.modifiers_pending = true;
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::ModifiersChanged(seat_state.modifiers.into()),
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
WlKeyboardEvent::RepeatInfo { rate, delay } => {
|
||||
keyboard_state.repeat_info = if rate == 0 {
|
||||
// Stop the repeat once we get a disable event.
|
||||
keyboard_state.current_repeat = None;
|
||||
if let Some(repeat_token) = keyboard_state.repeat_token.take() {
|
||||
keyboard_state.loop_handle.remove(repeat_token);
|
||||
}
|
||||
RepeatInfo::Disable
|
||||
} else {
|
||||
let gap = Duration::from_micros(1_000_000 / rate as u64);
|
||||
let delay = Duration::from_millis(delay as u64);
|
||||
RepeatInfo::Repeat { gap, delay }
|
||||
};
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the keyboard on the current seat.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyboardState {
|
||||
/// The underlying WlKeyboard.
|
||||
pub keyboard: WlKeyboard,
|
||||
|
||||
/// Loop handle to handle key repeat.
|
||||
pub loop_handle: LoopHandle<'static, WinitState>,
|
||||
|
||||
/// The state of the keyboard.
|
||||
pub xkb_context: Context,
|
||||
|
||||
/// The information about the repeat rate obtained from the compositor.
|
||||
pub repeat_info: RepeatInfo,
|
||||
|
||||
/// The token of the current handle inside the calloop's event loop.
|
||||
pub repeat_token: Option<RegistrationToken>,
|
||||
|
||||
/// The current repeat raw key.
|
||||
pub current_repeat: Option<u32>,
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn new(keyboard: WlKeyboard, loop_handle: LoopHandle<'static, WinitState>) -> Self {
|
||||
Self {
|
||||
keyboard,
|
||||
loop_handle,
|
||||
xkb_context: Context::new().unwrap(),
|
||||
repeat_info: RepeatInfo::default(),
|
||||
repeat_token: None,
|
||||
current_repeat: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KeyboardState {
|
||||
fn drop(&mut self) {
|
||||
if self.keyboard.version() >= 3 {
|
||||
self.keyboard.release();
|
||||
}
|
||||
|
||||
if let Some(token) = self.repeat_token.take() {
|
||||
self.loop_handle.remove(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The rate at which a pressed key is repeated.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RepeatInfo {
|
||||
/// Keys will be repeated at the specified rate and delay.
|
||||
Repeat {
|
||||
/// The time between the key repeats.
|
||||
gap: Duration,
|
||||
|
||||
/// Delay (in milliseconds) between a key press and the start of repetition.
|
||||
delay: Duration,
|
||||
},
|
||||
|
||||
/// Keys should not be repeated.
|
||||
Disable,
|
||||
}
|
||||
|
||||
impl Default for RepeatInfo {
|
||||
/// The default repeat rate is 25 keys per second with the delay of 200ms.
|
||||
///
|
||||
/// The values are picked based on the default in various compositors and Xorg.
|
||||
fn default() -> Self {
|
||||
Self::Repeat { gap: Duration::from_millis(40), delay: Duration::from_millis(200) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Keyboard user data.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyboardData {
|
||||
/// The currently focused window surface. Could be `None` on bugged compositors, like mutter.
|
||||
window_id: Mutex<Option<WindowId>>,
|
||||
|
||||
/// The seat used to create this keyboard.
|
||||
seat: WlSeat,
|
||||
}
|
||||
|
||||
impl KeyboardData {
|
||||
pub fn new(seat: WlSeat) -> Self {
|
||||
Self { window_id: Default::default(), seat }
|
||||
}
|
||||
}
|
||||
|
||||
fn key_input(
|
||||
keyboard_state: &mut KeyboardState,
|
||||
event_sink: &mut EventSink,
|
||||
data: &KeyboardData,
|
||||
keycode: u32,
|
||||
state: ElementState,
|
||||
repeat: bool,
|
||||
) {
|
||||
let window_id = match *data.window_id.lock().unwrap() {
|
||||
Some(window_id) => window_id,
|
||||
None => return,
|
||||
};
|
||||
|
||||
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: None, event, is_synthetic: false };
|
||||
event_sink.push_window_event(event, window_id);
|
||||
}
|
||||
}
|
||||
235
winit-wayland/src/seat/mod.rs
Normal file
235
winit-wayland/src/seat/mod.rs
Normal file
@@ -0,0 +1,235 @@
|
||||
//! Seat handling.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ahash::AHashMap;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3;
|
||||
use sctk::seat::pointer::{ThemeSpec, ThemedPointer};
|
||||
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
|
||||
use tracing::warn;
|
||||
use winit_core::event::WindowEvent;
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
mod keyboard;
|
||||
mod pointer;
|
||||
mod text_input;
|
||||
mod touch;
|
||||
|
||||
use keyboard::{KeyboardData, KeyboardState};
|
||||
pub use pointer::relative_pointer::RelativePointerState;
|
||||
pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt};
|
||||
use text_input::TextInputData;
|
||||
pub use text_input::{TextInputState, ZwpTextInputV3Ext};
|
||||
use touch::TouchPoint;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WinitSeatState {
|
||||
/// The pointer bound on the seat.
|
||||
pointer: Option<Arc<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
/// The touch bound on the seat.
|
||||
touch: Option<WlTouch>,
|
||||
|
||||
/// The mapping from touched points to the surfaces they're present.
|
||||
touch_map: AHashMap<i32, TouchPoint>,
|
||||
|
||||
/// Id of the first touch event.
|
||||
first_touch_id: Option<i32>,
|
||||
|
||||
/// The text input bound on the seat.
|
||||
text_input: Option<Arc<ZwpTextInputV3>>,
|
||||
|
||||
/// The relative pointer bound on the seat.
|
||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
||||
|
||||
/// The keyboard bound on the seat.
|
||||
keyboard_state: Option<KeyboardState>,
|
||||
|
||||
/// The current modifiers state on the seat.
|
||||
modifiers: ModifiersState,
|
||||
|
||||
/// Whether we have pending modifiers.
|
||||
modifiers_pending: bool,
|
||||
}
|
||||
|
||||
impl WinitSeatState {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for WinitState {
|
||||
fn seat_state(&mut self) -> &mut SeatState {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = match self.seats.get_mut(&seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_seat::new_capability for unknown seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch if seat_state.touch.is_none() => {
|
||||
seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok();
|
||||
},
|
||||
SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => {
|
||||
let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone()));
|
||||
seat_state.keyboard_state =
|
||||
Some(KeyboardState::new(keyboard, self.loop_handle.clone()));
|
||||
},
|
||||
SeatCapability::Pointer if seat_state.pointer.is_none() => {
|
||||
let surface = self.compositor_state.create_surface(queue_handle);
|
||||
let viewport = self
|
||||
.viewporter_state
|
||||
.as_ref()
|
||||
.map(|state| state.get_viewport(&surface, queue_handle));
|
||||
let surface_id = surface.id();
|
||||
let pointer_data = WinitPointerData::new(seat.clone(), viewport);
|
||||
let themed_pointer = self
|
||||
.seat_state
|
||||
.get_pointer_with_theme_and_data(
|
||||
queue_handle,
|
||||
&seat,
|
||||
self.shm.wl_shm(),
|
||||
surface,
|
||||
ThemeSpec::System,
|
||||
pointer_data,
|
||||
)
|
||||
.expect("failed to create pointer with present capability.");
|
||||
|
||||
seat_state.relative_pointer = self.relative_pointer.as_ref().map(|manager| {
|
||||
manager.get_relative_pointer(
|
||||
themed_pointer.pointer(),
|
||||
queue_handle,
|
||||
sctk::globals::GlobalData,
|
||||
)
|
||||
});
|
||||
|
||||
let themed_pointer = Arc::new(themed_pointer);
|
||||
|
||||
// Register cursor surface.
|
||||
self.pointer_surfaces.insert(surface_id, themed_pointer.clone());
|
||||
|
||||
seat_state.pointer = Some(themed_pointer);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Some(text_input_state) =
|
||||
seat_state.text_input.is_none().then_some(self.text_input_state.as_ref()).flatten()
|
||||
{
|
||||
seat_state.text_input = Some(Arc::new(text_input_state.get_text_input(
|
||||
&seat,
|
||||
queue_handle,
|
||||
TextInputData::default(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
capability: SeatCapability,
|
||||
) {
|
||||
let seat_state = match self.seats.get_mut(&seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_seat::remove_capability for unknown seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(text_input) = seat_state.text_input.take() {
|
||||
text_input.destroy();
|
||||
}
|
||||
|
||||
match capability {
|
||||
SeatCapability::Touch => {
|
||||
if let Some(touch) = seat_state.touch.take() {
|
||||
if touch.version() >= 3 {
|
||||
touch.release();
|
||||
}
|
||||
}
|
||||
},
|
||||
SeatCapability::Pointer => {
|
||||
if let Some(relative_pointer) = seat_state.relative_pointer.take() {
|
||||
relative_pointer.destroy();
|
||||
}
|
||||
|
||||
if let Some(pointer) = seat_state.pointer.take() {
|
||||
let pointer_data = pointer.pointer().winit_data();
|
||||
|
||||
// Remove the cursor from the mapping.
|
||||
let surface_id = pointer.surface().id();
|
||||
let _ = self.pointer_surfaces.remove(&surface_id);
|
||||
|
||||
// Remove the inner locks/confines before dropping the pointer.
|
||||
pointer_data.unlock_pointer();
|
||||
pointer_data.unconfine_pointer();
|
||||
|
||||
if pointer.pointer().version() >= 3 {
|
||||
pointer.pointer().release();
|
||||
}
|
||||
}
|
||||
},
|
||||
SeatCapability::Keyboard => {
|
||||
seat_state.keyboard_state = None;
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_seat(
|
||||
&mut self,
|
||||
_connection: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
self.seats.insert(seat.id(), WinitSeatState::new());
|
||||
}
|
||||
|
||||
fn remove_seat(
|
||||
&mut self,
|
||||
_connection: &Connection,
|
||||
_queue_handle: &QueueHandle<Self>,
|
||||
seat: WlSeat,
|
||||
) {
|
||||
let _ = self.seats.remove(&seat.id());
|
||||
self.on_keyboard_destroy(&seat.id());
|
||||
}
|
||||
}
|
||||
|
||||
impl WinitState {
|
||||
fn on_keyboard_destroy(&mut self, seat: &ObjectId) {
|
||||
for (window_id, window) in self.windows.get_mut() {
|
||||
let mut window = window.lock().unwrap();
|
||||
let had_focus = window.has_focus();
|
||||
window.remove_seat_focus(seat);
|
||||
if had_focus != window.has_focus() {
|
||||
self.events_sink.push_window_event(WindowEvent::Focused(false), *window_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sctk::delegate_seat!(WinitState);
|
||||
524
winit-wayland/src/seat/pointer/mod.rs
Normal file
524
winit-wayland/src/seat/pointer/mod.rs
Normal file
@@ -0,0 +1,524 @@
|
||||
//! The pointer events.
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use tracing::warn;
|
||||
|
||||
use sctk::reexports::client::delegate_dispatch;
|
||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle, Dispatch};
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
||||
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
|
||||
use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
|
||||
use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1};
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::csd_frame::FrameClick;
|
||||
use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport;
|
||||
|
||||
use sctk::compositor::SurfaceData;
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::seat::pointer::{
|
||||
PointerData, PointerDataExt, PointerEvent, PointerEventKind, PointerHandler,
|
||||
};
|
||||
use sctk::seat::SeatState;
|
||||
|
||||
use dpi::{LogicalPosition, PhysicalPosition};
|
||||
use winit_core::event::{
|
||||
ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase,
|
||||
WindowEvent,
|
||||
};
|
||||
|
||||
use crate::state::WinitState;
|
||||
use crate::WindowId;
|
||||
|
||||
pub mod relative_pointer;
|
||||
|
||||
impl PointerHandler for WinitState {
|
||||
fn pointer_frame(
|
||||
&mut self,
|
||||
connection: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
pointer: &WlPointer,
|
||||
events: &[PointerEvent],
|
||||
) {
|
||||
let seat = pointer.winit_data().seat();
|
||||
let seat_state = match self.seats.get(&seat.id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received pointer event without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let themed_pointer = match seat_state.pointer.as_ref() {
|
||||
Some(pointer) => pointer,
|
||||
None => {
|
||||
warn!("Received pointer event without pointer");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
for event in events {
|
||||
let surface = &event.surface;
|
||||
|
||||
// The parent surface.
|
||||
let parent_surface = match event.surface.data::<SurfaceData>() {
|
||||
Some(data) => data.parent_surface().unwrap_or(surface),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let window_id = crate::make_wid(parent_surface);
|
||||
|
||||
// Ensure that window exists.
|
||||
let mut window = match self.windows.get_mut().get_mut(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let scale_factor = window.scale_factor();
|
||||
let position: PhysicalPosition<f64> =
|
||||
LogicalPosition::new(event.position.0, event.position.1).to_physical(scale_factor);
|
||||
|
||||
match event.kind {
|
||||
// Pointer movements on decorations.
|
||||
PointerEventKind::Enter { .. } | PointerEventKind::Motion { .. }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
if let Some(icon) = window.frame_point_moved(
|
||||
seat,
|
||||
surface,
|
||||
Duration::ZERO,
|
||||
event.position.0,
|
||||
event.position.1,
|
||||
) {
|
||||
let _ = themed_pointer.set_cursor(connection, icon);
|
||||
}
|
||||
},
|
||||
PointerEventKind::Leave { .. } if parent_surface != surface => {
|
||||
window.frame_point_left();
|
||||
},
|
||||
ref kind @ PointerEventKind::Press { button, serial, time }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, time }
|
||||
if parent_surface != surface =>
|
||||
{
|
||||
let click = match wayland_button_to_winit(button) {
|
||||
MouseButton::Left => FrameClick::Normal,
|
||||
MouseButton::Right => FrameClick::Alternate,
|
||||
_ => continue,
|
||||
};
|
||||
let pressed = matches!(kind, PointerEventKind::Press { .. });
|
||||
|
||||
// Emulate click on the frame.
|
||||
window.frame_click(
|
||||
click,
|
||||
pressed,
|
||||
seat,
|
||||
serial,
|
||||
Duration::from_millis(time as u64),
|
||||
window_id,
|
||||
&mut self.window_compositor_updates,
|
||||
);
|
||||
},
|
||||
// Regular events on the main surface.
|
||||
PointerEventKind::Enter { .. } => {
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerEntered {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position,
|
||||
kind: PointerKind::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
|
||||
window.pointer_entered(Arc::downgrade(themed_pointer));
|
||||
|
||||
// Set the currently focused surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = Some(window_id);
|
||||
},
|
||||
PointerEventKind::Leave { .. } => {
|
||||
window.pointer_left(Arc::downgrade(themed_pointer));
|
||||
|
||||
// Remove the active surface.
|
||||
pointer.winit_data().inner.lock().unwrap().surface = None;
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerLeft {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
PointerEventKind::Motion { .. } => {
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerMoved {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
position,
|
||||
source: PointerSource::Mouse,
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
ref kind @ PointerEventKind::Press { button, serial, .. }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
|
||||
// Update the last button serial.
|
||||
pointer.winit_data().inner.lock().unwrap().latest_button_serial = serial;
|
||||
|
||||
let button = wayland_button_to_winit(button);
|
||||
let state = if matches!(kind, PointerEventKind::Press { .. }) {
|
||||
ElementState::Pressed
|
||||
} else {
|
||||
ElementState::Released
|
||||
};
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerButton {
|
||||
primary: true,
|
||||
device_id: None,
|
||||
state,
|
||||
position,
|
||||
button: button.into(),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
},
|
||||
PointerEventKind::Axis { horizontal, vertical, .. } => {
|
||||
// Get the current phase.
|
||||
let mut pointer_data = pointer.winit_data().inner.lock().unwrap();
|
||||
|
||||
let has_discrete_scroll = horizontal.discrete != 0 || vertical.discrete != 0;
|
||||
|
||||
// Figure out what to do about start/ended phases here.
|
||||
//
|
||||
// Figure out how to deal with `Started`. Also the `Ended` is not guaranteed
|
||||
// to be sent for mouse wheels.
|
||||
let phase = if horizontal.stop || vertical.stop {
|
||||
TouchPhase::Ended
|
||||
} else {
|
||||
match pointer_data.phase {
|
||||
// Discrete scroll only results in moved events.
|
||||
_ if has_discrete_scroll => TouchPhase::Moved,
|
||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
||||
_ => TouchPhase::Started,
|
||||
}
|
||||
};
|
||||
|
||||
// Update the phase.
|
||||
pointer_data.phase = phase;
|
||||
|
||||
// Mice events have both pixel and discrete delta's at the same time. So prefer
|
||||
// the discrete values if they are present.
|
||||
let delta = if has_discrete_scroll {
|
||||
// NOTE: Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::LineDelta(
|
||||
(-horizontal.discrete) as f32,
|
||||
(-vertical.discrete) as f32,
|
||||
)
|
||||
} else {
|
||||
// NOTE: Wayland sign convention is the inverse of winit.
|
||||
MouseScrollDelta::PixelDelta(
|
||||
LogicalPosition::new(-horizontal.absolute, -vertical.absolute)
|
||||
.to_physical(scale_factor),
|
||||
)
|
||||
};
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::MouseWheel { device_id: None, delta, phase },
|
||||
window_id,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPointerData {
|
||||
/// The inner winit data associated with the pointer.
|
||||
inner: Mutex<WinitPointerDataInner>,
|
||||
|
||||
/// The data required by the sctk.
|
||||
sctk_data: PointerData,
|
||||
|
||||
/// Viewport for fractional cursor.
|
||||
viewport: Option<WpViewport>,
|
||||
}
|
||||
|
||||
impl WinitPointerData {
|
||||
pub fn new(seat: WlSeat, viewport: Option<WpViewport>) -> Self {
|
||||
Self {
|
||||
inner: Mutex::new(WinitPointerDataInner::default()),
|
||||
sctk_data: PointerData::new(seat),
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lock_pointer(
|
||||
&self,
|
||||
pointer_constraints: &PointerConstraintsState,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if inner.locked_pointer.is_none() {
|
||||
inner.locked_pointer = Some(pointer_constraints.lock_pointer(
|
||||
surface,
|
||||
pointer,
|
||||
None,
|
||||
Lifetime::Persistent,
|
||||
queue_handle,
|
||||
GlobalData,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unlock_pointer(&self) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(locked_pointer) = inner.locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn confine_pointer(
|
||||
&self,
|
||||
pointer_constraints: &PointerConstraintsState,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
self.inner.lock().unwrap().confined_pointer = Some(pointer_constraints.confine_pointer(
|
||||
surface,
|
||||
pointer,
|
||||
None,
|
||||
Lifetime::Persistent,
|
||||
queue_handle,
|
||||
GlobalData,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn unconfine_pointer(&self) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
if let Some(confined_pointer) = inner.confined_pointer.as_ref() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Seat associated with this pointer.
|
||||
pub fn seat(&self) -> &WlSeat {
|
||||
self.sctk_data.seat()
|
||||
}
|
||||
|
||||
/// Active window.
|
||||
pub fn focused_window(&self) -> Option<WindowId> {
|
||||
self.inner.lock().unwrap().surface
|
||||
}
|
||||
|
||||
/// Last button serial.
|
||||
pub fn latest_button_serial(&self) -> u32 {
|
||||
self.sctk_data.latest_button_serial().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Last enter serial.
|
||||
pub fn latest_enter_serial(&self) -> u32 {
|
||||
self.sctk_data.latest_enter_serial().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn set_locked_cursor_position(&self, surface_x: f64, surface_y: f64) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
if let Some(locked_pointer) = inner.locked_pointer.as_ref() {
|
||||
locked_pointer.set_cursor_position_hint(surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn viewport(&self) -> Option<&WpViewport> {
|
||||
self.viewport.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(viewport) = self.viewport.take() {
|
||||
viewport.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerDataExt for WinitPointerData {
|
||||
fn pointer_data(&self) -> &PointerData {
|
||||
&self.sctk_data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPointerDataInner {
|
||||
/// The associated locked pointer.
|
||||
locked_pointer: Option<ZwpLockedPointerV1>,
|
||||
|
||||
/// The associated confined pointer.
|
||||
confined_pointer: Option<ZwpConfinedPointerV1>,
|
||||
|
||||
/// Serial of the last button event.
|
||||
latest_button_serial: u32,
|
||||
|
||||
/// Currently focused window.
|
||||
surface: Option<WindowId>,
|
||||
|
||||
/// Current axis phase.
|
||||
phase: TouchPhase,
|
||||
}
|
||||
|
||||
impl Drop for WinitPointerDataInner {
|
||||
fn drop(&mut self) {
|
||||
if let Some(locked_pointer) = self.locked_pointer.take() {
|
||||
locked_pointer.destroy();
|
||||
}
|
||||
|
||||
if let Some(confined_pointer) = self.confined_pointer.take() {
|
||||
confined_pointer.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WinitPointerDataInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
surface: None,
|
||||
locked_pointer: None,
|
||||
confined_pointer: None,
|
||||
latest_button_serial: 0,
|
||||
phase: TouchPhase::Ended,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the Wayland button into winit.
|
||||
fn wayland_button_to_winit(button: u32) -> MouseButton {
|
||||
// These values are coming from <linux/input-event-codes.h>.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
const BTN_RIGHT: u32 = 0x111;
|
||||
const BTN_MIDDLE: u32 = 0x112;
|
||||
const BTN_SIDE: u32 = 0x113;
|
||||
const BTN_EXTRA: u32 = 0x114;
|
||||
const BTN_FORWARD: u32 = 0x115;
|
||||
const BTN_BACK: u32 = 0x116;
|
||||
|
||||
match button {
|
||||
BTN_LEFT => MouseButton::Left,
|
||||
BTN_RIGHT => MouseButton::Right,
|
||||
BTN_MIDDLE => MouseButton::Middle,
|
||||
BTN_BACK | BTN_SIDE => MouseButton::Back,
|
||||
BTN_FORWARD | BTN_EXTRA => MouseButton::Forward,
|
||||
button => MouseButton::Other(button as u16),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WinitPointerDataExt {
|
||||
fn winit_data(&self) -> &WinitPointerData;
|
||||
}
|
||||
|
||||
impl WinitPointerDataExt for WlPointer {
|
||||
fn winit_data(&self) -> &WinitPointerData {
|
||||
self.data::<WinitPointerData>().expect("failed to get pointer data.")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PointerConstraintsState {
|
||||
pointer_constraints: ZwpPointerConstraintsV1,
|
||||
}
|
||||
|
||||
impl PointerConstraintsState {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let pointer_constraints = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { pointer_constraints })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PointerConstraintsState {
|
||||
type Target = ZwpPointerConstraintsV1;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.pointer_constraints
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpPointerConstraintsV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpPointerConstraintsV1,
|
||||
_event: <ZwpPointerConstraintsV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpLockedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpLockedPointerV1,
|
||||
_event: <ZwpLockedPointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpConfinedPointerV1, GlobalData, WinitState> for PointerConstraintsState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpConfinedPointerV1,
|
||||
_event: <ZwpConfinedPointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpCursorShapeDeviceV1, GlobalData, WinitState> for SeatState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpCursorShapeDeviceV1,
|
||||
_: <WpCursorShapeDeviceV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("wp_cursor_shape_manager has no events")
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WpCursorShapeManagerV1, GlobalData, WinitState> for SeatState {
|
||||
fn event(
|
||||
_: &mut WinitState,
|
||||
_: &WpCursorShapeManagerV1,
|
||||
_: <WpCursorShapeManagerV1 as Proxy>::Event,
|
||||
_: &GlobalData,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<WinitState>,
|
||||
) {
|
||||
unreachable!("wp_cursor_device_manager has no events")
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState);
|
||||
delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState);
|
||||
77
winit-wayland/src/seat/pointer/relative_pointer.rs
Normal file
77
winit-wayland/src/seat/pointer/relative_pointer.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
//! Relative pointer.
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::{delegate_dispatch, Dispatch};
|
||||
use sctk::reexports::client::{Connection, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::relative_pointer::zv1::{
|
||||
client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
client::zwp_relative_pointer_v1::{self, ZwpRelativePointerV1},
|
||||
};
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
|
||||
use winit_core::event::DeviceEvent;
|
||||
use crate::state::WinitState;
|
||||
|
||||
/// Wrapper around the relative pointer.
|
||||
#[derive(Debug)]
|
||||
pub struct RelativePointerState {
|
||||
manager: ZwpRelativePointerManagerV1,
|
||||
}
|
||||
|
||||
impl RelativePointerState {
|
||||
/// Create new relative pointer manager.
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { manager })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RelativePointerState {
|
||||
type Target = ZwpRelativePointerManagerV1;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpRelativePointerManagerV1, GlobalData, WinitState> for RelativePointerState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpRelativePointerManagerV1,
|
||||
_event: <ZwpRelativePointerManagerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpRelativePointerV1, GlobalData, WinitState> for RelativePointerState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
_proxy: &ZwpRelativePointerV1,
|
||||
event: <ZwpRelativePointerV1 as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let (dx_unaccel, dy_unaccel) = match event {
|
||||
zwp_relative_pointer_v1::Event::RelativeMotion { dx_unaccel, dy_unaccel, .. } => {
|
||||
(dx_unaccel, dy_unaccel)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
state
|
||||
.events_sink
|
||||
.push_device_event(DeviceEvent::PointerMotion { delta: (dx_unaccel, dy_unaccel) });
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpRelativePointerV1: GlobalData] => RelativePointerState);
|
||||
delegate_dispatch!(WinitState: [ZwpRelativePointerManagerV1: GlobalData] => RelativePointerState);
|
||||
199
winit-wayland/src/seat/text_input/mod.rs
Normal file
199
winit-wayland/src/seat/text_input/mod.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use sctk::globals::GlobalData;
|
||||
use sctk::reexports::client::globals::{BindError, GlobalList};
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::{delegate_dispatch, Connection, Dispatch, Proxy, QueueHandle};
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
||||
use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
|
||||
ContentHint, ContentPurpose, Event as TextInputEvent, ZwpTextInputV3,
|
||||
};
|
||||
use winit_core::event::{Ime, WindowEvent};
|
||||
use winit_core::window::ImePurpose;
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextInputState {
|
||||
text_input_manager: ZwpTextInputManagerV3,
|
||||
}
|
||||
|
||||
impl TextInputState {
|
||||
pub fn new(
|
||||
globals: &GlobalList,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
) -> Result<Self, BindError> {
|
||||
let text_input_manager = globals.bind(queue_handle, 1..=1, GlobalData)?;
|
||||
Ok(Self { text_input_manager })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TextInputState {
|
||||
type Target = ZwpTextInputManagerV3;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.text_input_manager
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTextInputManagerV3, GlobalData, WinitState> for TextInputState {
|
||||
fn event(
|
||||
_state: &mut WinitState,
|
||||
_proxy: &ZwpTextInputManagerV3,
|
||||
_event: <ZwpTextInputManagerV3 as Proxy>::Event,
|
||||
_data: &GlobalData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpTextInputV3, TextInputData, WinitState> for TextInputState {
|
||||
fn event(
|
||||
state: &mut WinitState,
|
||||
text_input: &ZwpTextInputV3,
|
||||
event: <ZwpTextInputV3 as Proxy>::Event,
|
||||
data: &TextInputData,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<WinitState>,
|
||||
) {
|
||||
let windows = state.windows.get_mut();
|
||||
let mut text_input_data = data.inner.lock().unwrap();
|
||||
match event {
|
||||
TextInputEvent::Enter { surface } => {
|
||||
let window_id = crate::make_wid(&surface);
|
||||
text_input_data.surface = Some(surface);
|
||||
|
||||
let mut window = match windows.get(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if window.ime_allowed() {
|
||||
text_input.enable();
|
||||
text_input.set_content_type_by_purpose(window.ime_purpose());
|
||||
text_input.commit();
|
||||
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
||||
}
|
||||
|
||||
window.text_input_entered(text_input);
|
||||
},
|
||||
TextInputEvent::Leave { surface } => {
|
||||
text_input_data.surface = None;
|
||||
|
||||
// Always issue a disable.
|
||||
text_input.disable();
|
||||
text_input.commit();
|
||||
|
||||
let window_id = crate::make_wid(&surface);
|
||||
|
||||
// XXX this check is essential, because `leave` could have a
|
||||
// reference to nil surface...
|
||||
let mut window = match windows.get(&window_id) {
|
||||
Some(window) => window.lock().unwrap(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
window.text_input_left(text_input);
|
||||
|
||||
state.events_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
||||
},
|
||||
TextInputEvent::PreeditString { text, cursor_begin, cursor_end } => {
|
||||
let text = text.unwrap_or_default();
|
||||
let cursor_begin = usize::try_from(cursor_begin)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
|
||||
let cursor_end = usize::try_from(cursor_end)
|
||||
.ok()
|
||||
.and_then(|idx| text.is_char_boundary(idx).then_some(idx));
|
||||
|
||||
text_input_data.pending_preedit = Some(Preedit { text, cursor_begin, cursor_end })
|
||||
},
|
||||
TextInputEvent::CommitString { text } => {
|
||||
text_input_data.pending_preedit = None;
|
||||
text_input_data.pending_commit = text;
|
||||
},
|
||||
TextInputEvent::Done { .. } => {
|
||||
let window_id = match text_input_data.surface.as_ref() {
|
||||
Some(surface) => crate::make_wid(surface),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Clear preedit, unless all we'll be doing next is sending a new preedit.
|
||||
if text_input_data.pending_commit.is_some()
|
||||
|| text_input_data.pending_preedit.is_none()
|
||||
{
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(String::new(), None)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
// Send `Commit`.
|
||||
if let Some(text) = text_input_data.pending_commit.take() {
|
||||
state
|
||||
.events_sink
|
||||
.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
||||
}
|
||||
|
||||
// Send preedit.
|
||||
if let Some(preedit) = text_input_data.pending_preedit.take() {
|
||||
let cursor_range =
|
||||
preedit.cursor_begin.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
||||
|
||||
state.events_sink.push_window_event(
|
||||
WindowEvent::Ime(Ime::Preedit(preedit.text, cursor_range)),
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
},
|
||||
TextInputEvent::DeleteSurroundingText { .. } => {
|
||||
// Not handled.
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ZwpTextInputV3Ext {
|
||||
fn set_content_type_by_purpose(&self, purpose: ImePurpose);
|
||||
}
|
||||
|
||||
impl ZwpTextInputV3Ext for ZwpTextInputV3 {
|
||||
fn set_content_type_by_purpose(&self, purpose: ImePurpose) {
|
||||
let (hint, purpose) = match purpose {
|
||||
ImePurpose::Password => (ContentHint::SensitiveData, ContentPurpose::Password),
|
||||
ImePurpose::Terminal => (ContentHint::None, ContentPurpose::Terminal),
|
||||
_ => (ContentHint::None, ContentPurpose::Normal),
|
||||
};
|
||||
self.set_content_type(hint, purpose);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Data associated with the text input.
|
||||
#[derive(Default)]
|
||||
pub struct TextInputData {
|
||||
inner: std::sync::Mutex<TextInputDataInner>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextInputDataInner {
|
||||
/// The `WlSurface` we're performing input to.
|
||||
surface: Option<WlSurface>,
|
||||
|
||||
/// The commit to submit on `done`.
|
||||
pending_commit: Option<String>,
|
||||
|
||||
/// The preedit to submit on `done`.
|
||||
pending_preedit: Option<Preedit>,
|
||||
}
|
||||
|
||||
/// The state of the preedit.
|
||||
struct Preedit {
|
||||
text: String,
|
||||
cursor_begin: Option<usize>,
|
||||
cursor_end: Option<usize>,
|
||||
}
|
||||
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputManagerV3: GlobalData] => TextInputState);
|
||||
delegate_dispatch!(WinitState: [ZwpTextInputV3: TextInputData] => TextInputState);
|
||||
254
winit-wayland/src/seat/touch/mod.rs
Normal file
254
winit-wayland/src/seat/touch/mod.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
//! Touch handling.
|
||||
|
||||
use dpi::LogicalPosition;
|
||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
||||
use sctk::reexports::client::{Connection, Proxy, QueueHandle};
|
||||
use sctk::seat::touch::{TouchData, TouchHandler};
|
||||
use tracing::warn;
|
||||
use winit_core::event::{
|
||||
ButtonSource, ElementState, FingerId, PointerKind, PointerSource, WindowEvent,
|
||||
};
|
||||
|
||||
use crate::state::WinitState;
|
||||
|
||||
impl TouchHandler for WinitState {
|
||||
fn down(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
_: u32,
|
||||
surface: WlSurface,
|
||||
id: i32,
|
||||
position: (f64, f64),
|
||||
) {
|
||||
let window_id = crate::make_wid(&surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_touch::down without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Update the state of the point.
|
||||
let location = LogicalPosition::<f64>::from(position);
|
||||
// Only update primary finger once we don't have any touch.
|
||||
if seat_state.touch_map.is_empty() {
|
||||
seat_state.first_touch_id = Some(id);
|
||||
}
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
seat_state.touch_map.insert(id, TouchPoint { surface, location });
|
||||
|
||||
let position = location.to_physical(scale_factor);
|
||||
let finger_id = FingerId::from_raw(id as usize);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerEntered {
|
||||
device_id: None,
|
||||
primary,
|
||||
position,
|
||||
kind: PointerKind::Touch(finger_id),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Pressed,
|
||||
position,
|
||||
button: ButtonSource::Touch { finger_id, force: None },
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
_: u32,
|
||||
id: i32,
|
||||
) {
|
||||
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_touch::up without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.remove(&id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Update the primary touch point.
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
// Reset primary finger once all the other fingers are lifted to not transfer primary
|
||||
// finger to some other finger and still accept it when it's briefly moved between the
|
||||
// windows.
|
||||
if seat_state.touch_map.is_empty() {
|
||||
seat_state.first_touch_id = None;
|
||||
}
|
||||
|
||||
let window_id = crate::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let position = touch_point.location.to_physical(scale_factor);
|
||||
let finger_id = FingerId::from_raw(id as usize);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerButton {
|
||||
device_id: None,
|
||||
primary,
|
||||
state: ElementState::Released,
|
||||
position,
|
||||
button: ButtonSource::Touch { finger_id, force: None },
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Touch(finger_id),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
touch: &WlTouch,
|
||||
_: u32,
|
||||
id: i32,
|
||||
position: (f64, f64),
|
||||
) {
|
||||
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_touch::motion without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// Remove the touch point.
|
||||
let touch_point = match seat_state.touch_map.get_mut(&id) {
|
||||
Some(touch_point) => touch_point,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
|
||||
let window_id = crate::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
touch_point.location = LogicalPosition::<f64>::from(position);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerMoved {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: touch_point.location.to_physical(scale_factor),
|
||||
source: PointerSource::Touch {
|
||||
finger_id: FingerId::from_raw(id as usize),
|
||||
force: None,
|
||||
},
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Connection, _: &QueueHandle<Self>, touch: &WlTouch) {
|
||||
let seat_state = match self.seats.get_mut(&touch.seat().id()) {
|
||||
Some(seat_state) => seat_state,
|
||||
None => {
|
||||
warn!("Received wl_touch::cancel without seat");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
for (id, touch_point) in seat_state.touch_map.drain() {
|
||||
let window_id = crate::make_wid(&touch_point.surface);
|
||||
let scale_factor = match self.windows.get_mut().get(&window_id) {
|
||||
Some(window) => window.lock().unwrap().scale_factor(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let primary = seat_state.first_touch_id == Some(id);
|
||||
let position = touch_point.location.to_physical(scale_factor);
|
||||
|
||||
self.events_sink.push_window_event(
|
||||
WindowEvent::PointerLeft {
|
||||
device_id: None,
|
||||
primary,
|
||||
position: Some(position),
|
||||
kind: PointerKind::Touch(FingerId::from_raw(id as usize)),
|
||||
},
|
||||
window_id,
|
||||
);
|
||||
}
|
||||
|
||||
seat_state.first_touch_id = None;
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: &WlTouch,
|
||||
_: i32,
|
||||
_: f64,
|
||||
_: f64,
|
||||
) {
|
||||
// Blank.
|
||||
}
|
||||
|
||||
fn orientation(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlTouch, _: i32, _: f64) {
|
||||
// Blank.
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the touch point.
|
||||
#[derive(Debug)]
|
||||
pub struct TouchPoint {
|
||||
/// The surface on which the point is present.
|
||||
pub surface: WlSurface,
|
||||
|
||||
/// The location of the point on the surface.
|
||||
pub location: LogicalPosition<f64>,
|
||||
}
|
||||
|
||||
pub trait TouchDataExt {
|
||||
fn seat(&self) -> &WlSeat;
|
||||
}
|
||||
|
||||
impl TouchDataExt for WlTouch {
|
||||
fn seat(&self) -> &WlSeat {
|
||||
self.data::<TouchData>().expect("failed to get touch data.").seat()
|
||||
}
|
||||
}
|
||||
|
||||
sctk::delegate_touch!(WinitState);
|
||||
Reference in New Issue
Block a user