Merge pull request #164 from mahkoh/jorth/input-method
seat: implement input methods
This commit is contained in:
commit
af3280652e
52 changed files with 2564 additions and 290 deletions
|
|
@ -141,10 +141,12 @@ Jay supports the following wayland protocols:
|
|||
| zwlr_layer_shell_v1 | 4[^no_exclusive] | Yes |
|
||||
| zwlr_screencopy_manager_v1 | 3 | Yes |
|
||||
| zwp_idle_inhibit_manager_v1 | 1 | |
|
||||
| zwp_input_method_manager_v2 | 1 | Yes |
|
||||
| zwp_linux_dmabuf_v1 | 5 | |
|
||||
| zwp_pointer_constraints_v1 | 1 | |
|
||||
| zwp_primary_selection_device_manager_v1 | 1 | |
|
||||
| zwp_relative_pointer_manager_v1 | 1 | |
|
||||
| zwp_text_input_manager_v3 | 1 | |
|
||||
| zwp_virtual_keyboard_manager_v1 | 1 | Yes |
|
||||
| zxdg_decoration_manager_v1 | 1 | |
|
||||
| zxdg_output_manager_v1 | 3 | |
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
- Add support for wp-alpha-modifier.
|
||||
- Add support for per-device keymaps.
|
||||
- Add support for virtual-keyboard-unstable-v1.
|
||||
- Add support for zwp_input_method_manager_v2.
|
||||
- Add support for zwp_text_input_manager_v3.
|
||||
|
||||
# 1.0.3 (2024-04-11)
|
||||
|
||||
|
|
|
|||
|
|
@ -593,6 +593,7 @@ impl MetalConnector {
|
|||
Some(output.global.pos.get()),
|
||||
Some(rr),
|
||||
output.global.persistent.scale.get(),
|
||||
true,
|
||||
render_hw_cursor,
|
||||
output.has_fullscreen(),
|
||||
output.global.persistent.transform.get(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use {
|
|||
globals::Globals,
|
||||
ifs::{
|
||||
wl_output::{OutputId, PersistentOutputState, WlOutputGlobal},
|
||||
wl_surface::NoneSurfaceExt,
|
||||
wl_surface::{zwp_input_popup_surface_v2::input_popup_positioning, NoneSurfaceExt},
|
||||
},
|
||||
io_uring::{IoUring, IoUringError},
|
||||
leaks,
|
||||
|
|
@ -172,6 +172,7 @@ fn start_compositor2(
|
|||
pending_output_render_data: Default::default(),
|
||||
pending_float_layout: Default::default(),
|
||||
pending_float_titles: Default::default(),
|
||||
pending_input_popup_positioning: Default::default(),
|
||||
dbus: Dbus::new(&engine, &ring, &run_toplevel),
|
||||
fdcloser: FdCloser::new(),
|
||||
logger: logger.clone(),
|
||||
|
|
@ -230,6 +231,7 @@ fn start_compositor2(
|
|||
subsurface_ids: Default::default(),
|
||||
wait_for_sync_obj: Rc::new(WaitForSyncObj::new(&ring, &engine)),
|
||||
explicit_sync_enabled: Cell::new(true),
|
||||
keyboard_state_ids: Default::default(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
@ -326,6 +328,7 @@ fn start_global_event_handlers(
|
|||
eng.spawn2(Phase::Layout, float_layout(state.clone())),
|
||||
eng.spawn2(Phase::PostLayout, float_titles(state.clone())),
|
||||
eng.spawn2(Phase::PostLayout, idle(state.clone(), backend.clone())),
|
||||
eng.spawn2(Phase::PostLayout, input_popup_positioning(state.clone())),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -309,13 +309,12 @@ impl ConfigProxyHandler {
|
|||
keymap: Keymap,
|
||||
) -> Result<(), CphError> {
|
||||
let dev = self.get_device_handler_data(device)?;
|
||||
if keymap.is_invalid() {
|
||||
dev.keymap_id.set(None);
|
||||
dev.keymap.set(None);
|
||||
let map = if keymap.is_invalid() {
|
||||
None
|
||||
} else {
|
||||
let map = self.get_keymap(keymap)?;
|
||||
dev.set_keymap(&map);
|
||||
Some(self.get_keymap(keymap)?)
|
||||
};
|
||||
dev.set_keymap(map);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -548,7 +547,7 @@ impl ConfigProxyHandler {
|
|||
Some(self.get_seat(seat)?)
|
||||
};
|
||||
let dev = self.get_device_handler_data(device)?;
|
||||
dev.seat.set(seat);
|
||||
dev.set_seat(seat);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ impl dyn GfxFramebuffer {
|
|||
cursor_rect: Option<Rect>,
|
||||
result: Option<&mut RenderResult>,
|
||||
scale: Scale,
|
||||
render_cursor: bool,
|
||||
render_hardware_cursor: bool,
|
||||
black_background: bool,
|
||||
transform: Transform,
|
||||
|
|
@ -347,6 +348,18 @@ impl dyn GfxFramebuffer {
|
|||
let seats = state.globals.lock_seats();
|
||||
for seat in seats.values() {
|
||||
let (mut x, mut y) = seat.get_position();
|
||||
if let Some(im) = seat.input_method() {
|
||||
for (_, popup) in &im.popups {
|
||||
if popup.surface.node_visible() {
|
||||
let pos = popup.surface.buffer_abs_pos.get();
|
||||
let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1());
|
||||
if extents.intersects(&rect) {
|
||||
let (x, y) = rect.translate(pos.x1(), pos.y1());
|
||||
renderer.render_surface(&popup.surface, x, y, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(drag) = seat.toplevel_drag() {
|
||||
if let Some(tl) = drag.toplevel.get() {
|
||||
if tl.xdg.surface.buffer.get().is_some() {
|
||||
|
|
@ -368,12 +381,14 @@ impl dyn GfxFramebuffer {
|
|||
renderer.render_surface(&dnd_icon, x, y, None);
|
||||
}
|
||||
}
|
||||
if let Some(cursor) = seat.get_cursor() {
|
||||
if render_hardware_cursor || !seat.hardware_cursor() {
|
||||
cursor.tick();
|
||||
x -= Fixed::from_int(rect.x1());
|
||||
y -= Fixed::from_int(rect.y1());
|
||||
cursor.render(&mut renderer, x, y);
|
||||
if render_cursor {
|
||||
if let Some(cursor) = seat.get_cursor() {
|
||||
if render_hardware_cursor || !seat.hardware_cursor() {
|
||||
cursor.tick();
|
||||
x -= Fixed::from_int(rect.x1());
|
||||
y -= Fixed::from_int(rect.y1());
|
||||
cursor.render(&mut renderer, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -407,6 +422,7 @@ impl dyn GfxFramebuffer {
|
|||
cursor_rect,
|
||||
result,
|
||||
scale,
|
||||
true,
|
||||
render_hardware_cursor,
|
||||
node.has_fullscreen(),
|
||||
node.global.persistent.transform.get(),
|
||||
|
|
@ -420,6 +436,7 @@ impl dyn GfxFramebuffer {
|
|||
cursor_rect: Option<Rect>,
|
||||
result: Option<&mut RenderResult>,
|
||||
scale: Scale,
|
||||
render_cursor: bool,
|
||||
render_hardware_cursor: bool,
|
||||
black_background: bool,
|
||||
transform: Transform,
|
||||
|
|
@ -430,6 +447,7 @@ impl dyn GfxFramebuffer {
|
|||
cursor_rect,
|
||||
result,
|
||||
scale,
|
||||
render_cursor,
|
||||
render_hardware_cursor,
|
||||
black_background,
|
||||
transform,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,14 @@ use {
|
|||
wl_output::WlOutputGlobal,
|
||||
wl_registry::WlRegistry,
|
||||
wl_seat::{
|
||||
text_input::{
|
||||
zwp_input_method_manager_v2::ZwpInputMethodManagerV2Global,
|
||||
zwp_text_input_manager_v3::ZwpTextInputManagerV3Global,
|
||||
},
|
||||
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1Global,
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1Global,
|
||||
zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1Global, WlSeatGlobal,
|
||||
zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1Global,
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_shm::WlShmGlobal,
|
||||
wl_subcompositor::WlSubcompositorGlobal,
|
||||
|
|
@ -177,6 +182,8 @@ impl Globals {
|
|||
add_singleton!(ZwlrDataControlManagerV1Global);
|
||||
add_singleton!(WpAlphaModifierV1Global);
|
||||
add_singleton!(ZwpVirtualKeyboardManagerV1Global);
|
||||
add_singleton!(ZwpInputMethodManagerV2Global);
|
||||
add_singleton!(ZwpTextInputManagerV3Global);
|
||||
}
|
||||
|
||||
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ impl JayInputRequestHandler for JayInput {
|
|||
self.or_error(|| {
|
||||
let seat = self.seat(req.seat)?;
|
||||
let dev = self.device(req.id)?;
|
||||
dev.seat.set(Some(seat));
|
||||
dev.set_seat(Some(seat));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
@ -333,7 +333,7 @@ impl JayInputRequestHandler for JayInput {
|
|||
fn detach(&self, req: Detach, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.or_error(|| {
|
||||
let dev = self.device(req.id)?;
|
||||
dev.seat.set(None);
|
||||
dev.set_seat(None);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ impl JayInputRequestHandler for JayInput {
|
|||
fn set_device_keymap(&self, req: SetDeviceKeymap, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.set_keymap_impl(&req.keymap, req.keymap_len, |map| {
|
||||
let dev = self.device(req.id)?;
|
||||
dev.set_keymap(&map);
|
||||
dev.set_keymap(Some(map.clone()));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mod event_handling;
|
||||
mod kb_owner;
|
||||
mod pointer_owner;
|
||||
pub mod text_input;
|
||||
pub mod wl_keyboard;
|
||||
pub mod wl_pointer;
|
||||
pub mod wl_touch;
|
||||
|
|
@ -37,6 +38,10 @@ use {
|
|||
wl_seat::{
|
||||
kb_owner::KbOwnerHolder,
|
||||
pointer_owner::PointerOwnerHolder,
|
||||
text_input::{
|
||||
zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2,
|
||||
zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3,
|
||||
},
|
||||
wl_keyboard::{WlKeyboard, WlKeyboardError, REPEAT_INFO_SINCE},
|
||||
wl_pointer::WlPointer,
|
||||
wl_touch::WlTouch,
|
||||
|
|
@ -49,7 +54,7 @@ use {
|
|||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::Rect,
|
||||
state::State,
|
||||
state::{DeviceHandlerData, State},
|
||||
time::now_usec,
|
||||
tree::{
|
||||
generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode,
|
||||
|
|
@ -58,14 +63,14 @@ use {
|
|||
utils::{
|
||||
asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap,
|
||||
errorfmt::ErrorFmt, linkedlist::LinkedNode, numcell::NumCell, rc_eq::rc_eq,
|
||||
smallmap::SmallMap, transform_ext::TransformExt, vecset::VecSet,
|
||||
smallmap::SmallMap, transform_ext::TransformExt,
|
||||
},
|
||||
wire::{
|
||||
wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId,
|
||||
WlSeatId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id,
|
||||
ZwpRelativePointerV1Id,
|
||||
ZwpRelativePointerV1Id, ZwpTextInputV3Id,
|
||||
},
|
||||
xkbcommon::{KeymapId, XkbKeymap, XkbState},
|
||||
xkbcommon::{DynKeyboardState, KeyboardState, KeymapId, XkbKeymap, XkbState},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
jay_config::keyboard::mods::Modifiers,
|
||||
|
|
@ -75,10 +80,10 @@ use {
|
|||
collections::hash_map::Entry,
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
rc::{Rc, Weak},
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::{c, Errno, OwnedFd},
|
||||
uapi::OwnedFd,
|
||||
};
|
||||
|
||||
pub const POINTER: u32 = 1;
|
||||
|
|
@ -130,7 +135,6 @@ pub struct WlSeatGlobal {
|
|||
pointer_stack_modified: Cell<bool>,
|
||||
found_tree: RefCell<Vec<FoundNode>>,
|
||||
keyboard_node: CloneCell<Rc<dyn Node>>,
|
||||
pressed_keys: RefCell<VecSet<u32>>,
|
||||
bindings: RefCell<AHashMap<ClientId, AHashMap<WlSeatId, Rc<WlSeat>>>>,
|
||||
x_data_devices: SmallMap<XIpcDeviceId, Rc<XIpcDevice>, 1>,
|
||||
data_devices: RefCell<AHashMap<ClientId, AHashMap<WlDataDeviceId, Rc<WlDataDevice>>>>,
|
||||
|
|
@ -144,10 +148,9 @@ pub struct WlSeatGlobal {
|
|||
CopyHashMap<(ClientId, ZwlrDataControlDeviceV1Id), Rc<ZwlrDataControlDeviceV1>>,
|
||||
repeat_rate: Cell<(i32, i32)>,
|
||||
seat_kb_map: CloneCell<Rc<XkbKeymap>>,
|
||||
seat_kb_map_id: Cell<KeymapId>,
|
||||
effective_kb_map: CloneCell<Rc<XkbKeymap>>,
|
||||
effective_kb_map_id: Cell<KeymapId>,
|
||||
kb_state: RefCell<XkbState>,
|
||||
seat_xkb_state: CloneCell<Rc<RefCell<XkbState>>>,
|
||||
latest_kb_state: CloneCell<Rc<dyn DynKeyboardState>>,
|
||||
xkb_states: CopyHashMap<KeymapId, Weak<RefCell<XkbState>>>,
|
||||
cursor: CloneCell<Option<Rc<dyn Cursor>>>,
|
||||
tree_changed: Rc<AsyncEvent>,
|
||||
selection: CloneCell<Option<Rc<dyn DynDataSource>>>,
|
||||
|
|
@ -168,7 +171,10 @@ pub struct WlSeatGlobal {
|
|||
constraint: CloneCell<Option<Rc<SeatConstraint>>>,
|
||||
idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc<ExtIdleNotificationV1>>,
|
||||
last_input_usec: Cell<u64>,
|
||||
keymap_version: NumCell<u32>,
|
||||
text_inputs: RefCell<AHashMap<ClientId, CopyHashMap<ZwpTextInputV3Id, Rc<ZwpTextInputV3>>>>,
|
||||
text_input: CloneCell<Option<Rc<ZwpTextInputV3>>>,
|
||||
input_method: CloneCell<Option<Rc<ZwpInputMethodV2>>>,
|
||||
input_method_grab: CloneCell<Option<Rc<ZwpInputMethodKeyboardGrabV2>>>,
|
||||
}
|
||||
|
||||
const CHANGE_CURSOR_MOVED: u32 = 1 << 0;
|
||||
|
|
@ -182,6 +188,13 @@ impl Drop for WlSeatGlobal {
|
|||
|
||||
impl WlSeatGlobal {
|
||||
pub fn new(name: GlobalName, seat_name: &str, state: &Rc<State>) -> Rc<Self> {
|
||||
let seat_xkb_state = state
|
||||
.default_keymap
|
||||
.state(state.keyboard_state_ids.next())
|
||||
.map(|s| Rc::new(RefCell::new(s)))
|
||||
.unwrap();
|
||||
let xkb_states = CopyHashMap::new();
|
||||
xkb_states.set(state.default_keymap.id, Rc::downgrade(&seat_xkb_state));
|
||||
let slf = Rc::new(Self {
|
||||
id: state.seat_ids.next(),
|
||||
name,
|
||||
|
|
@ -196,17 +209,15 @@ impl WlSeatGlobal {
|
|||
pointer_stack_modified: Cell::new(false),
|
||||
found_tree: RefCell::new(vec![]),
|
||||
keyboard_node: CloneCell::new(state.root.clone()),
|
||||
pressed_keys: RefCell::new(Default::default()),
|
||||
bindings: Default::default(),
|
||||
x_data_devices: Default::default(),
|
||||
data_devices: RefCell::new(Default::default()),
|
||||
primary_selection_devices: RefCell::new(Default::default()),
|
||||
repeat_rate: Cell::new((25, 250)),
|
||||
seat_kb_map: CloneCell::new(state.default_keymap.clone()),
|
||||
seat_kb_map_id: Cell::new(state.default_keymap.id),
|
||||
effective_kb_map: CloneCell::new(state.default_keymap.clone()),
|
||||
effective_kb_map_id: Cell::new(state.default_keymap.id),
|
||||
kb_state: RefCell::new(state.default_keymap.state().unwrap()),
|
||||
seat_xkb_state: CloneCell::new(seat_xkb_state.clone()),
|
||||
latest_kb_state: CloneCell::new(seat_xkb_state.clone()),
|
||||
xkb_states,
|
||||
cursor: Default::default(),
|
||||
tree_changed: Default::default(),
|
||||
selection: Default::default(),
|
||||
|
|
@ -228,7 +239,10 @@ impl WlSeatGlobal {
|
|||
idle_notifications: Default::default(),
|
||||
last_input_usec: Cell::new(now_usec()),
|
||||
wlr_data_devices: Default::default(),
|
||||
keymap_version: NumCell::new(1),
|
||||
text_inputs: Default::default(),
|
||||
text_input: Default::default(),
|
||||
input_method: Default::default(),
|
||||
input_method_grab: Default::default(),
|
||||
});
|
||||
state.add_cursor_size(*DEFAULT_CURSOR_SIZE);
|
||||
let seat = slf.clone();
|
||||
|
|
@ -249,6 +263,10 @@ impl WlSeatGlobal {
|
|||
self.seat_kb_map.get()
|
||||
}
|
||||
|
||||
pub fn input_method(&self) -> Option<Rc<ZwpInputMethodV2>> {
|
||||
self.input_method.get()
|
||||
}
|
||||
|
||||
pub fn toplevel_drag(&self) -> Option<Rc<XdgToplevelDragV1>> {
|
||||
self.pointer_owner.toplevel_drag()
|
||||
}
|
||||
|
|
@ -516,25 +534,49 @@ impl WlSeatGlobal {
|
|||
}
|
||||
|
||||
pub fn set_seat_keymap(&self, keymap: &Rc<XkbKeymap>) {
|
||||
let Some(xkb_state) = self.get_xkb_state(keymap) else {
|
||||
return;
|
||||
};
|
||||
self.seat_kb_map.set(keymap.clone());
|
||||
self.seat_kb_map_id.set(keymap.id);
|
||||
let old = self.seat_xkb_state.set(xkb_state.clone());
|
||||
if !rc_eq(&old, &xkb_state) {
|
||||
self.handle_xkb_state_change(&old.borrow(), &xkb_state.borrow());
|
||||
}
|
||||
}
|
||||
|
||||
fn set_effective_keymap(&self, keymap: &Rc<XkbKeymap>) {
|
||||
let state = match keymap.state() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("Could not create keymap state: {}", ErrorFmt(e));
|
||||
return;
|
||||
}
|
||||
fn handle_xkb_state_change(&self, old: &XkbState, new: &XkbState) {
|
||||
let Some(surface) = self.keyboard_node.get().node_into_surface() else {
|
||||
return;
|
||||
};
|
||||
self.keyboard_node.get().node_on_unfocus(self);
|
||||
self.effective_kb_map.set(keymap.clone());
|
||||
self.effective_kb_map_id.set(keymap.id);
|
||||
*self.kb_state.borrow_mut() = state;
|
||||
self.keymap_version.fetch_add(1);
|
||||
self.pressed_keys.borrow_mut().clear();
|
||||
self.keyboard_node.get().node_on_focus(self);
|
||||
let serial = surface.client.next_serial();
|
||||
self.surface_kb_event(Version::ALL, &surface, |kb| {
|
||||
if kb.kb_state_id() == old.kb_state.id {
|
||||
kb.send_leave(serial, surface.id);
|
||||
kb.enter(serial, surface.id, &new.kb_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_xkb_state(&self, keymap: &Rc<XkbKeymap>) -> Option<Rc<RefCell<XkbState>>> {
|
||||
if let Some(weak) = self.xkb_states.get(&keymap.id) {
|
||||
if let Some(state) = weak.upgrade() {
|
||||
return Some(state);
|
||||
}
|
||||
}
|
||||
self.xkb_states
|
||||
.lock()
|
||||
.retain(|_, state| state.strong_count() > 0);
|
||||
match keymap.state(self.state.keyboard_state_ids.next()) {
|
||||
Ok(s) => {
|
||||
let s = Rc::new(RefCell::new(s));
|
||||
self.xkb_states.set(keymap.id, Rc::downgrade(&s));
|
||||
Some(s)
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Could not create xkb state: {}", ErrorFmt(e));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_for_lock(self: &Rc<Self>) {
|
||||
|
|
@ -706,6 +748,9 @@ impl WlSeatGlobal {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some(grab) = self.input_method_grab.get() {
|
||||
grab.send_repeat_info();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(self: &Rc<Self>) {
|
||||
|
|
@ -1023,6 +1068,10 @@ impl WlSeatGlobal {
|
|||
self.tree_changed_handler.set(None);
|
||||
self.output.set(self.state.dummy_output.get().unwrap());
|
||||
self.constraint.take();
|
||||
self.text_inputs.borrow_mut().clear();
|
||||
self.text_input.take();
|
||||
self.input_method.take();
|
||||
self.input_method_grab.take();
|
||||
}
|
||||
|
||||
pub fn id(&self) -> SeatId {
|
||||
|
|
@ -1091,6 +1140,11 @@ impl WlSeatGlobal {
|
|||
tl.tl_set_visible(visible);
|
||||
}
|
||||
}
|
||||
if let Some(im) = self.input_method.get() {
|
||||
for (_, popup) in &im.popups {
|
||||
popup.update_visible();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1146,25 +1200,11 @@ impl WlSeat {
|
|||
self.global.move_(node);
|
||||
}
|
||||
|
||||
pub fn keymap_fd(&self, keymap: &XkbKeymap) -> Result<Rc<OwnedFd>, WlKeyboardError> {
|
||||
pub fn keymap_fd(&self, state: &KeyboardState) -> Result<Rc<OwnedFd>, WlKeyboardError> {
|
||||
if self.version >= READ_ONLY_KEYMAP_SINCE {
|
||||
return Ok(keymap.map.clone());
|
||||
return Ok(state.map.clone());
|
||||
}
|
||||
let fd = match uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => return Err(WlKeyboardError::KeymapMemfd(e.into())),
|
||||
};
|
||||
let target = keymap.map_len as c::off_t;
|
||||
let mut pos = 0;
|
||||
while pos < target {
|
||||
let rem = target - pos;
|
||||
let res = uapi::sendfile(fd.raw(), keymap.map.raw(), Some(&mut pos), rem as usize);
|
||||
match res {
|
||||
Ok(_) | Err(Errno(c::EINTR)) => {}
|
||||
Err(e) => return Err(WlKeyboardError::KeymapCopy(e.into())),
|
||||
}
|
||||
}
|
||||
Ok(Rc::new(fd))
|
||||
Ok(state.create_new_keymap_fd()?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1184,11 +1224,13 @@ impl WlSeatRequestHandler for WlSeat {
|
|||
track!(self.client, p);
|
||||
self.client.add_client_obj(&p)?;
|
||||
self.keyboards.set(req.id, p.clone());
|
||||
p.send_keymap();
|
||||
if let Some(surface) = self.global.keyboard_node.get().node_into_surface() {
|
||||
if surface.client.id == self.client.id {
|
||||
let serial = self.client.next_serial();
|
||||
p.send_enter(serial, surface.id, &self.global.pressed_keys.borrow())
|
||||
p.enter(
|
||||
self.client.next_serial(),
|
||||
surface.id,
|
||||
&self.global.seat_xkb_state.get().borrow().kb_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.version >= REPEAT_INFO_SINCE {
|
||||
|
|
@ -1269,3 +1311,51 @@ pub fn collect_kb_foci(node: Rc<dyn Node>) -> SmallVec<[Rc<WlSeatGlobal>; 3]> {
|
|||
collect_kb_foci2(node, &mut res);
|
||||
res
|
||||
}
|
||||
|
||||
impl DeviceHandlerData {
|
||||
pub fn set_seat(&self, seat: Option<Rc<WlSeatGlobal>>) {
|
||||
let old = self.seat.set(seat.clone());
|
||||
if let Some(old) = old {
|
||||
if let Some(new) = seat {
|
||||
if old.id() == new.id() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let xkb_state = self.get_effective_xkb_state(&old);
|
||||
let xkb_state = &mut *xkb_state.borrow_mut();
|
||||
xkb_state.reset();
|
||||
old.handle_xkb_state_change(xkb_state, xkb_state);
|
||||
}
|
||||
self.update_xkb_state();
|
||||
}
|
||||
|
||||
pub fn set_keymap(&self, keymap: Option<Rc<XkbKeymap>>) {
|
||||
self.keymap.set(keymap);
|
||||
self.update_xkb_state();
|
||||
}
|
||||
|
||||
fn get_effective_xkb_state(&self, seat: &WlSeatGlobal) -> Rc<RefCell<XkbState>> {
|
||||
match self.xkb_state.get() {
|
||||
Some(s) => s,
|
||||
_ => seat.seat_xkb_state.get(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_xkb_state(&self) {
|
||||
let Some(seat) = self.seat.get() else {
|
||||
self.xkb_state.take();
|
||||
return;
|
||||
};
|
||||
let old = self.get_effective_xkb_state(&seat);
|
||||
self.xkb_state.take();
|
||||
if let Some(keymap) = self.keymap.get() {
|
||||
if let Some(state) = seat.get_xkb_state(&keymap) {
|
||||
self.xkb_state.set(Some(state));
|
||||
}
|
||||
}
|
||||
let new = self.get_effective_xkb_state(&seat);
|
||||
if !rc_eq(&old, &new) {
|
||||
seat.handle_xkb_state_change(&old.borrow(), &new.borrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use {
|
|||
DynDataSource,
|
||||
},
|
||||
wl_seat::{
|
||||
text_input::TextDisconnectReason,
|
||||
wl_keyboard::{self, WlKeyboard},
|
||||
wl_pointer::{
|
||||
self, PendingScroll, WlPointer, AXIS_DISCRETE_SINCE_VERSION,
|
||||
|
|
@ -32,15 +33,16 @@ use {
|
|||
tree::{Direction, FloatNode, Node, ToplevelNode},
|
||||
utils::{bitflags::BitflagsExt, smallmap::SmallMap},
|
||||
wire::WlDataOfferId,
|
||||
xkbcommon::{ModifierState, XKB_KEY_DOWN, XKB_KEY_UP},
|
||||
xkbcommon::{KeyboardState, XkbState, XKB_KEY_DOWN, XKB_KEY_UP},
|
||||
},
|
||||
isnt::std_1::primitive::IsntSlice2Ext,
|
||||
jay_config::keyboard::{
|
||||
mods::{Modifiers, CAPS, NUM},
|
||||
syms::KeySym,
|
||||
ModifiedKeySym,
|
||||
},
|
||||
smallvec::SmallVec,
|
||||
std::rc::Rc,
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -196,20 +198,7 @@ impl WlSeatGlobal {
|
|||
time_usec,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
let desired_kb_map_id = match dev.keymap_id.get() {
|
||||
Some(id) => id,
|
||||
None => self.seat_kb_map_id.get(),
|
||||
};
|
||||
if desired_kb_map_id != self.effective_kb_map_id.get() {
|
||||
let map = match dev.keymap.get() {
|
||||
Some(map) => map,
|
||||
None => self.seat_kb_map.get(),
|
||||
};
|
||||
self.set_effective_keymap(&map);
|
||||
}
|
||||
self.key_event(time_usec, key, state)
|
||||
}
|
||||
} => self.key_event(time_usec, key, state, || dev.get_effective_xkb_state(self)),
|
||||
InputEvent::ConnectorPosition {
|
||||
time_usec,
|
||||
connector,
|
||||
|
|
@ -356,18 +345,27 @@ impl WlSeatGlobal {
|
|||
self.pointer_owner.button(self, time_usec, button, state);
|
||||
}
|
||||
|
||||
pub(super) fn key_event(&self, time_usec: u64, key: u32, key_state: KeyState) {
|
||||
pub(super) fn key_event<F>(
|
||||
&self,
|
||||
time_usec: u64,
|
||||
key: u32,
|
||||
key_state: KeyState,
|
||||
mut get_state: F,
|
||||
) where
|
||||
F: FnMut() -> Rc<RefCell<XkbState>>,
|
||||
{
|
||||
let mut xkb_state_rc = get_state();
|
||||
let mut xkb_state = xkb_state_rc.borrow_mut();
|
||||
let (state, xkb_dir) = {
|
||||
let mut pk = self.pressed_keys.borrow_mut();
|
||||
match key_state {
|
||||
KeyState::Released => {
|
||||
if !pk.remove(&key) {
|
||||
if xkb_state.kb_state.pressed_keys.not_contains(&key) {
|
||||
return;
|
||||
}
|
||||
(wl_keyboard::RELEASED, XKB_KEY_UP)
|
||||
}
|
||||
KeyState::Pressed => {
|
||||
if !pk.insert(key) {
|
||||
if xkb_state.kb_state.pressed_keys.contains(&key) {
|
||||
return;
|
||||
}
|
||||
(wl_keyboard::PRESSED, XKB_KEY_DOWN)
|
||||
|
|
@ -377,10 +375,9 @@ impl WlSeatGlobal {
|
|||
let mut shortcuts = SmallVec::<[_; 1]>::new();
|
||||
let new_mods;
|
||||
{
|
||||
let mut kb_state = self.kb_state.borrow_mut();
|
||||
if !self.state.lock.locked.get() && state == wl_keyboard::PRESSED {
|
||||
let old_mods = kb_state.mods();
|
||||
let keysyms = kb_state.unmodified_keysyms(key);
|
||||
let old_mods = xkb_state.mods();
|
||||
let keysyms = xkb_state.unmodified_keysyms(key);
|
||||
for &sym in keysyms {
|
||||
let mods = old_mods.mods_effective & !(CAPS.0 | NUM.0);
|
||||
if let Some(mods) = self.shortcuts.get(&(mods, sym)) {
|
||||
|
|
@ -391,44 +388,49 @@ impl WlSeatGlobal {
|
|||
}
|
||||
}
|
||||
}
|
||||
new_mods = kb_state.update(key, xkb_dir);
|
||||
new_mods = xkb_state.update(key, xkb_dir);
|
||||
}
|
||||
self.state.for_each_seat_tester(|t| {
|
||||
t.send_key(self.id, time_usec, key, key_state);
|
||||
});
|
||||
let node = self.keyboard_node.get();
|
||||
let input_method_grab = self.input_method_grab.get();
|
||||
if shortcuts.is_empty() {
|
||||
node.node_on_key(self, time_usec, key, state);
|
||||
match &input_method_grab {
|
||||
Some(g) => g.on_key(time_usec, key, state, &xkb_state.kb_state),
|
||||
_ => node.node_on_key(self, time_usec, key, state, &xkb_state.kb_state),
|
||||
}
|
||||
} else if let Some(config) = self.state.config.get() {
|
||||
let id = xkb_state.kb_state.id;
|
||||
drop(xkb_state);
|
||||
for shortcut in shortcuts {
|
||||
config.invoke_shortcut(self.id(), &shortcut);
|
||||
}
|
||||
xkb_state_rc = get_state();
|
||||
xkb_state = xkb_state_rc.borrow_mut();
|
||||
if id != xkb_state.kb_state.id {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(mods) = new_mods {
|
||||
if new_mods {
|
||||
self.state.for_each_seat_tester(|t| {
|
||||
t.send_modifiers(self.id, &mods);
|
||||
t.send_modifiers(self.id, &xkb_state.kb_state.mods);
|
||||
});
|
||||
node.node_on_mods(self, mods);
|
||||
match &input_method_grab {
|
||||
Some(g) => g.on_modifiers(&xkb_state.kb_state),
|
||||
_ => node.node_on_mods(self, &xkb_state.kb_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_modifiers(
|
||||
&self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) {
|
||||
let new_mods =
|
||||
self.kb_state
|
||||
.borrow_mut()
|
||||
.set(mods_depressed, mods_latched, mods_locked, group);
|
||||
if let Some(mods) = new_mods {
|
||||
self.state.for_each_seat_tester(|t| {
|
||||
t.send_modifiers(self.id, &mods);
|
||||
});
|
||||
self.keyboard_node.get().node_on_mods(self, mods);
|
||||
match key_state {
|
||||
KeyState::Released => {
|
||||
xkb_state.kb_state.pressed_keys.remove(&key);
|
||||
}
|
||||
KeyState::Pressed => {
|
||||
xkb_state.kb_state.pressed_keys.insert(key);
|
||||
}
|
||||
}
|
||||
drop(xkb_state);
|
||||
self.latest_kb_state.set(xkb_state_rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -578,7 +580,7 @@ impl WlSeatGlobal {
|
|||
});
|
||||
}
|
||||
|
||||
fn surface_kb_event<F>(&self, ver: Version, surface: &WlSurface, mut f: F)
|
||||
pub fn surface_kb_event<F>(&self, ver: Version, surface: &WlSurface, mut f: F)
|
||||
where
|
||||
F: FnMut(&Rc<WlKeyboard>),
|
||||
{
|
||||
|
|
@ -766,6 +768,18 @@ impl WlSeatGlobal {
|
|||
// Unfocus callbacks
|
||||
impl WlSeatGlobal {
|
||||
pub fn unfocus_surface(&self, surface: &WlSurface) {
|
||||
if let Some(ti) = self.text_input.take() {
|
||||
if let Some(con) = ti.connection.get() {
|
||||
con.disconnect(TextDisconnectReason::FocusLost);
|
||||
}
|
||||
}
|
||||
if let Some(tis) = self.text_inputs.borrow().get(&surface.client.id) {
|
||||
for ti in tis.lock().values() {
|
||||
ti.send_leave(surface);
|
||||
ti.send_done();
|
||||
}
|
||||
}
|
||||
|
||||
let serial = surface.client.next_serial();
|
||||
self.surface_kb_event(Version::ALL, surface, |k| k.send_leave(serial, surface.id))
|
||||
}
|
||||
|
|
@ -774,21 +788,11 @@ impl WlSeatGlobal {
|
|||
// Focus callbacks
|
||||
impl WlSeatGlobal {
|
||||
pub fn focus_surface(&self, surface: &WlSurface) {
|
||||
let pressed_keys = &*self.pressed_keys.borrow();
|
||||
let kb_state = self.latest_kb_state.get();
|
||||
let kb_state = &*kb_state.borrow();
|
||||
let serial = surface.client.next_serial();
|
||||
self.surface_kb_event(Version::ALL, surface, |k| {
|
||||
k.send_enter(serial, surface.id, pressed_keys)
|
||||
});
|
||||
let ModifierState {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
..
|
||||
} = self.kb_state.borrow().mods();
|
||||
let serial = surface.client.next_serial();
|
||||
self.surface_kb_event(Version::ALL, surface, |k| {
|
||||
k.send_modifiers(serial, mods_depressed, mods_latched, mods_locked, group)
|
||||
k.enter(serial, surface.id, kb_state);
|
||||
});
|
||||
|
||||
if self.keyboard_node.get().node_client_id() != Some(surface.client.id) {
|
||||
|
|
@ -801,32 +805,40 @@ impl WlSeatGlobal {
|
|||
&surface.client,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(tis) = self.text_inputs.borrow_mut().get(&surface.client.id) {
|
||||
for ti in tis.lock().values() {
|
||||
ti.send_enter(surface);
|
||||
ti.send_done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key callbacks
|
||||
impl WlSeatGlobal {
|
||||
pub fn key_surface(&self, surface: &WlSurface, time_usec: u64, key: u32, state: u32) {
|
||||
pub fn key_surface(
|
||||
&self,
|
||||
surface: &WlSurface,
|
||||
time_usec: u64,
|
||||
key: u32,
|
||||
state: u32,
|
||||
kb_state: &KeyboardState,
|
||||
) {
|
||||
let serial = surface.client.next_serial();
|
||||
let time = (time_usec / 1000) as _;
|
||||
self.surface_kb_event(Version::ALL, surface, |k| {
|
||||
k.send_key(serial, time, key, state)
|
||||
k.on_key(serial, time, key, state, surface.id, kb_state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiers callbacks
|
||||
impl WlSeatGlobal {
|
||||
pub fn mods_surface(&self, surface: &WlSurface, mods: ModifierState) {
|
||||
pub fn mods_surface(&self, surface: &WlSurface, kb_state: &KeyboardState) {
|
||||
let serial = surface.client.next_serial();
|
||||
self.surface_kb_event(Version::ALL, surface, |k| {
|
||||
k.send_modifiers(
|
||||
serial,
|
||||
mods.mods_depressed,
|
||||
mods.mods_latched,
|
||||
mods.mods_locked,
|
||||
mods.group,
|
||||
)
|
||||
k.on_mods_changed(serial, surface.id, kb_state)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
96
src/ifs/wl_seat/text_input.rs
Normal file
96
src/ifs/wl_seat/text_input.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use {
|
||||
crate::ifs::{
|
||||
wl_seat::{
|
||||
text_input::{
|
||||
zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3,
|
||||
},
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub mod zwp_input_method_keyboard_grab_v2;
|
||||
pub mod zwp_input_method_manager_v2;
|
||||
pub mod zwp_input_method_v2;
|
||||
pub mod zwp_text_input_manager_v3;
|
||||
pub mod zwp_text_input_v3;
|
||||
|
||||
const MAX_TEXT_SIZE: usize = 4000;
|
||||
|
||||
pub struct TextInputConnection {
|
||||
pub seat: Rc<WlSeatGlobal>,
|
||||
pub text_input: Rc<ZwpTextInputV3>,
|
||||
pub input_method: Rc<ZwpInputMethodV2>,
|
||||
pub surface: Rc<WlSurface>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TextConnectReason {
|
||||
TextInputEnabled,
|
||||
InputMethodCreated,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TextDisconnectReason {
|
||||
FocusLost,
|
||||
TextInputDisabled,
|
||||
InputMethodDestroyed,
|
||||
}
|
||||
|
||||
impl WlSeatGlobal {
|
||||
fn create_text_input_connection(self: &Rc<Self>, text_connect_reason: TextConnectReason) {
|
||||
let Some(im) = self.input_method.get() else {
|
||||
return;
|
||||
};
|
||||
let Some(ti) = self.text_input.get() else {
|
||||
return;
|
||||
};
|
||||
let Some(surface) = self.keyboard_node.get().node_into_surface() else {
|
||||
log::warn!("Seat has text input but keyboard node is not a surface");
|
||||
return;
|
||||
};
|
||||
if surface.client.id != ti.client.id {
|
||||
log::warn!("Seat's text input belongs to different client than the keyboard node");
|
||||
return;
|
||||
}
|
||||
let con = Rc::new(TextInputConnection {
|
||||
seat: self.clone(),
|
||||
text_input: ti.clone(),
|
||||
input_method: im.clone(),
|
||||
surface: surface.clone(),
|
||||
});
|
||||
con.connect(text_connect_reason);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextInputConnection {
|
||||
fn connect(self: &Rc<Self>, reason: TextConnectReason) {
|
||||
self.input_method.connection.set(Some(self.clone()));
|
||||
self.text_input.connection.set(Some(self.clone()));
|
||||
self.surface
|
||||
.text_input_connections
|
||||
.insert(self.seat.id, self.clone());
|
||||
|
||||
self.input_method.activate();
|
||||
if reason == TextConnectReason::InputMethodCreated {
|
||||
self.text_input.send_all_to(&self.input_method);
|
||||
self.input_method.send_done();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(&self, reason: TextDisconnectReason) {
|
||||
self.text_input.connection.take();
|
||||
self.input_method.connection.take();
|
||||
self.surface.text_input_connections.remove(&self.seat.id);
|
||||
|
||||
if reason != TextDisconnectReason::InputMethodDestroyed {
|
||||
self.input_method.send_deactivate();
|
||||
self.input_method.send_done();
|
||||
for (_, popup) in &self.input_method.popups {
|
||||
popup.update_visible();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs
Normal file
126
src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::wl_seat::{text_input::zwp_input_method_v2::ZwpInputMethodV2, wl_keyboard},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::errorfmt::ErrorFmt,
|
||||
wire::{zwp_input_method_keyboard_grab_v2::*, ZwpInputMethodKeyboardGrabV2Id},
|
||||
xkbcommon::{KeyboardState, KeyboardStateId},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpInputMethodKeyboardGrabV2 {
|
||||
pub id: ZwpInputMethodKeyboardGrabV2Id,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
pub input_method: Rc<ZwpInputMethodV2>,
|
||||
pub kb_state_id: Cell<KeyboardStateId>,
|
||||
}
|
||||
|
||||
impl ZwpInputMethodKeyboardGrabV2 {
|
||||
fn detach(&self) {
|
||||
self.input_method.seat.input_method_grab.take();
|
||||
}
|
||||
|
||||
fn send_keymap(&self, kb_state: &KeyboardState) {
|
||||
let map = match kb_state.create_new_keymap_fd() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
log::error!("Could not create new keymap fd: {}", ErrorFmt(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.client.event(Keymap {
|
||||
self_id: self.id,
|
||||
format: wl_keyboard::XKB_V1,
|
||||
fd: map,
|
||||
size: kb_state.map_len as _,
|
||||
});
|
||||
}
|
||||
|
||||
fn update_state(&self, serial: u32, kb_state: &KeyboardState) {
|
||||
self.send_keymap(kb_state);
|
||||
self.send_modifiers(serial, kb_state);
|
||||
self.kb_state_id.set(kb_state.id);
|
||||
}
|
||||
|
||||
pub fn on_key(&self, time_usec: u64, key: u32, state: u32, kb_state: &KeyboardState) {
|
||||
let serial = self.client.next_serial();
|
||||
if self.kb_state_id.get() != kb_state.id {
|
||||
self.update_state(serial, kb_state);
|
||||
}
|
||||
self.send_key(serial, time_usec, key, state);
|
||||
}
|
||||
|
||||
fn send_key(&self, serial: u32, time_usec: u64, key: u32, state: u32) {
|
||||
self.client.event(Key {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
time: (time_usec / 1000) as _,
|
||||
key,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_modifiers(&self, kb_state: &KeyboardState) {
|
||||
let serial = self.client.next_serial();
|
||||
if self.kb_state_id.get() != kb_state.id {
|
||||
self.update_state(serial, kb_state);
|
||||
}
|
||||
self.send_modifiers(serial, kb_state);
|
||||
}
|
||||
|
||||
fn send_modifiers(&self, serial: u32, kb_state: &KeyboardState) {
|
||||
self.client.event(Modifiers {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
mods_depressed: kb_state.mods.mods_depressed,
|
||||
mods_latched: kb_state.mods.mods_latched,
|
||||
mods_locked: kb_state.mods.mods_locked,
|
||||
group: kb_state.mods.group,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_repeat_info(&self) {
|
||||
let (rate, delay) = self.input_method.seat.repeat_rate.get();
|
||||
self.client.event(RepeatInfo {
|
||||
self_id: self.id,
|
||||
rate,
|
||||
delay,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwpInputMethodKeyboardGrabV2RequestHandler for ZwpInputMethodKeyboardGrabV2 {
|
||||
type Error = ZwpInputMethodKeyboardGrabV2Error;
|
||||
|
||||
fn release(&self, _req: Release, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpInputMethodKeyboardGrabV2;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpInputMethodKeyboardGrabV2 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_obj!(ZwpInputMethodKeyboardGrabV2);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpInputMethodKeyboardGrabV2Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwpInputMethodKeyboardGrabV2Error, ClientError);
|
||||
116
src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs
Normal file
116
src/ifs/wl_seat/text_input/zwp_input_method_manager_v2.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::wl_seat::text_input::{zwp_input_method_v2::ZwpInputMethodV2, TextConnectReason},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{zwp_input_method_manager_v2::*, ZwpInputMethodManagerV2Id},
|
||||
},
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpInputMethodManagerV2Global {
|
||||
pub name: GlobalName,
|
||||
}
|
||||
|
||||
pub struct ZwpInputMethodManagerV2 {
|
||||
pub id: ZwpInputMethodManagerV2Id,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl ZwpInputMethodManagerV2Global {
|
||||
pub fn new(name: GlobalName) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
|
||||
fn bind_(
|
||||
self: Rc<Self>,
|
||||
id: ZwpInputMethodManagerV2Id,
|
||||
client: &Rc<Client>,
|
||||
version: Version,
|
||||
) -> Result<(), ZwpTextInputManagerV3Error> {
|
||||
let obj = Rc::new(ZwpInputMethodManagerV2 {
|
||||
id,
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
});
|
||||
track!(client, obj);
|
||||
client.add_client_obj(&obj)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
global_base!(
|
||||
ZwpInputMethodManagerV2Global,
|
||||
ZwpInputMethodManagerV2,
|
||||
ZwpTextInputManagerV3Error
|
||||
);
|
||||
|
||||
impl Global for ZwpInputMethodManagerV2Global {
|
||||
fn singleton(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_global!(ZwpInputMethodManagerV2Global);
|
||||
|
||||
impl ZwpInputMethodManagerV2RequestHandler for ZwpInputMethodManagerV2 {
|
||||
type Error = ZwpTextInputManagerV3Error;
|
||||
|
||||
fn get_input_method(&self, req: GetInputMethod, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let seat = self.client.lookup(req.seat)?;
|
||||
let inert = seat.global.input_method.is_some();
|
||||
let im = Rc::new(ZwpInputMethodV2 {
|
||||
id: req.input_method,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
seat: seat.global.clone(),
|
||||
popups: Default::default(),
|
||||
connection: Default::default(),
|
||||
inert,
|
||||
num_done: Default::default(),
|
||||
pending: Default::default(),
|
||||
});
|
||||
track!(self.client, im);
|
||||
self.client.add_client_obj(&im)?;
|
||||
if inert {
|
||||
im.send_unavailable();
|
||||
} else {
|
||||
seat.global.input_method.set(Some(im));
|
||||
seat.global
|
||||
.create_text_input_connection(TextConnectReason::InputMethodCreated);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpInputMethodManagerV2;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpInputMethodManagerV2 {}
|
||||
|
||||
simple_add_obj!(ZwpInputMethodManagerV2);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpTextInputManagerV3Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwpTextInputManagerV3Error, ClientError);
|
||||
234
src/ifs/wl_seat/text_input/zwp_input_method_v2.rs
Normal file
234
src/ifs/wl_seat/text_input/zwp_input_method_v2.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::{
|
||||
wl_seat::{
|
||||
text_input::{
|
||||
zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2,
|
||||
TextDisconnectReason, TextInputConnection, MAX_TEXT_SIZE,
|
||||
},
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_surface::zwp_input_popup_surface_v2::{
|
||||
ZwpInputPopupSurfaceV2, ZwpInputPopupSurfaceV2Error,
|
||||
},
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::{clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap},
|
||||
wire::{zwp_input_method_v2::*, ZwpInputMethodV2Id, ZwpInputPopupSurfaceV2Id},
|
||||
xkbcommon::KeyboardStateId,
|
||||
},
|
||||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpInputMethodV2 {
|
||||
pub id: ZwpInputMethodV2Id,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
pub seat: Rc<WlSeatGlobal>,
|
||||
pub popups: SmallMap<ZwpInputPopupSurfaceV2Id, Rc<ZwpInputPopupSurfaceV2>, 1>,
|
||||
pub connection: CloneCell<Option<Rc<TextInputConnection>>>,
|
||||
pub inert: bool,
|
||||
pub num_done: NumCell<u32>,
|
||||
pub pending: RefCell<Pending>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pending {
|
||||
commit_string: Option<String>,
|
||||
delete_surrounding_text: Option<(u32, u32)>,
|
||||
preedit_string: Option<(String, i32, i32)>,
|
||||
}
|
||||
|
||||
impl ZwpInputMethodV2 {
|
||||
fn detach(&self) {
|
||||
if let Some(con) = self.connection.get() {
|
||||
con.disconnect(TextDisconnectReason::InputMethodDestroyed);
|
||||
}
|
||||
self.popups.clear();
|
||||
if !self.inert {
|
||||
self.seat.input_method.take();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn activate(&self) {
|
||||
self.pending.take();
|
||||
self.send_activate();
|
||||
}
|
||||
|
||||
pub fn send_activate(&self) {
|
||||
self.client.event(Activate { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_deactivate(&self) {
|
||||
self.client.event(Deactivate { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_surrounding_text(&self, text: &str, cursor: u32, anchor: u32) {
|
||||
self.client.event(SurroundingText {
|
||||
self_id: self.id,
|
||||
text,
|
||||
cursor,
|
||||
anchor,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_text_change_cause(&self, cause: u32) {
|
||||
self.client.event(TextChangeCause {
|
||||
self_id: self.id,
|
||||
cause,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_content_type(&self, hint: u32, purpose: u32) {
|
||||
self.client.event(ContentType {
|
||||
self_id: self.id,
|
||||
hint,
|
||||
purpose,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_done(&self) {
|
||||
self.num_done.fetch_add(1);
|
||||
self.client.event(Done { self_id: self.id });
|
||||
}
|
||||
|
||||
pub fn send_unavailable(&self) {
|
||||
self.client.event(Unavailable { self_id: self.id });
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {
|
||||
type Error = ZwpInputMethodV2Error;
|
||||
|
||||
fn commit_string(&self, req: CommitString<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if req.text.len() > MAX_TEXT_SIZE {
|
||||
return Err(ZwpInputMethodV2Error::TooLarge);
|
||||
}
|
||||
self.pending.borrow_mut().commit_string = Some(req.text.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_preedit_string(
|
||||
&self,
|
||||
req: SetPreeditString<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
if req.text.len() > MAX_TEXT_SIZE {
|
||||
return Err(ZwpInputMethodV2Error::TooLarge);
|
||||
}
|
||||
self.pending.borrow_mut().preedit_string =
|
||||
Some((req.text.to_string(), req.cursor_begin, req.cursor_end));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_surrounding_text(
|
||||
&self,
|
||||
req: DeleteSurroundingText,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.pending.borrow_mut().delete_surrounding_text =
|
||||
Some((req.before_length, req.after_length));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&self, req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if req.serial != self.num_done.get() {
|
||||
return Ok(());
|
||||
}
|
||||
let pending = self.pending.take();
|
||||
let Some(con) = self.connection.get() else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some(dst) = pending.delete_surrounding_text {
|
||||
con.text_input.send_delete_surrounding_text(dst.0, dst.1);
|
||||
}
|
||||
if let Some(dst) = pending.preedit_string {
|
||||
con.text_input
|
||||
.send_preedit_string(Some(&dst.0), dst.1, dst.2);
|
||||
}
|
||||
if let Some(dst) = pending.commit_string {
|
||||
con.text_input.send_commit_string(Some(&dst));
|
||||
}
|
||||
con.text_input.send_done();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_input_popup_surface(
|
||||
&self,
|
||||
req: GetInputPopupSurface,
|
||||
slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let surface = self.client.lookup(req.surface)?;
|
||||
let popup = Rc::new(ZwpInputPopupSurfaceV2 {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
input_method: slf.clone(),
|
||||
surface,
|
||||
version: self.version,
|
||||
tracker: Default::default(),
|
||||
positioning_scheduled: Cell::new(false),
|
||||
});
|
||||
track!(self.client, popup);
|
||||
self.client.add_client_obj(&popup)?;
|
||||
popup.install()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn grab_keyboard(&self, req: GrabKeyboard, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
if self.seat.input_method_grab.is_some() {
|
||||
return Err(ZwpInputMethodV2Error::HasGrab);
|
||||
}
|
||||
let grab = Rc::new(ZwpInputMethodKeyboardGrabV2 {
|
||||
id: req.keyboard,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
input_method: slf.clone(),
|
||||
kb_state_id: Cell::new(KeyboardStateId::from_raw(0)),
|
||||
});
|
||||
track!(self.client, grab);
|
||||
self.client.add_client_obj(&grab)?;
|
||||
grab.send_repeat_info();
|
||||
self.seat.input_method_grab.set(Some(grab));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpInputMethodV2;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpInputMethodV2 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_obj!(ZwpInputMethodV2);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpInputMethodV2Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error(transparent)]
|
||||
ZwpInputPopupSurfaceV2Error(#[from] ZwpInputPopupSurfaceV2Error),
|
||||
#[error("Text is larger than {} bytes", MAX_TEXT_SIZE)]
|
||||
TooLarge,
|
||||
#[error("Seat already has a grab")]
|
||||
HasGrab,
|
||||
}
|
||||
efrom!(ZwpInputMethodV2Error, ClientError);
|
||||
114
src/ifs/wl_seat/text_input/zwp_text_input_manager_v3.rs
Normal file
114
src/ifs/wl_seat/text_input/zwp_text_input_manager_v3.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::wl_seat::text_input::zwp_text_input_v3::ZwpTextInputV3,
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{zwp_text_input_manager_v3::*, ZwpTextInputManagerV3Id},
|
||||
},
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpTextInputManagerV3Global {
|
||||
pub name: GlobalName,
|
||||
}
|
||||
|
||||
pub struct ZwpTextInputManagerV3 {
|
||||
pub id: ZwpTextInputManagerV3Id,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl ZwpTextInputManagerV3Global {
|
||||
pub fn new(name: GlobalName) -> Self {
|
||||
Self { name }
|
||||
}
|
||||
|
||||
fn bind_(
|
||||
self: Rc<Self>,
|
||||
id: ZwpTextInputManagerV3Id,
|
||||
client: &Rc<Client>,
|
||||
version: Version,
|
||||
) -> Result<(), ZwpTextInputManagerV3Error> {
|
||||
let obj = Rc::new(ZwpTextInputManagerV3 {
|
||||
id,
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
});
|
||||
track!(client, obj);
|
||||
client.add_client_obj(&obj)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
global_base!(
|
||||
ZwpTextInputManagerV3Global,
|
||||
ZwpTextInputManagerV3,
|
||||
ZwpTextInputManagerV3Error
|
||||
);
|
||||
|
||||
impl Global for ZwpTextInputManagerV3Global {
|
||||
fn singleton(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_global!(ZwpTextInputManagerV3Global);
|
||||
|
||||
impl ZwpTextInputManagerV3RequestHandler for ZwpTextInputManagerV3 {
|
||||
type Error = ZwpTextInputManagerV3Error;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_text_input(&self, req: GetTextInput, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
let seat = self.client.lookup(req.seat)?;
|
||||
let ti = Rc::new(ZwpTextInputV3::new(
|
||||
req.id,
|
||||
&self.client,
|
||||
&seat.global,
|
||||
self.version,
|
||||
));
|
||||
track!(self.client, ti);
|
||||
self.client.add_client_obj(&ti)?;
|
||||
seat.global
|
||||
.text_inputs
|
||||
.borrow_mut()
|
||||
.entry(self.client.id)
|
||||
.or_default()
|
||||
.set(req.id, ti.clone());
|
||||
if let Some(surface) = seat.global.keyboard_node.get().node_into_surface() {
|
||||
if surface.client.id == self.client.id {
|
||||
ti.send_enter(&surface);
|
||||
ti.send_done();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpTextInputManagerV3;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpTextInputManagerV3 {}
|
||||
|
||||
simple_add_obj!(ZwpTextInputManagerV3);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpTextInputManagerV3Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
}
|
||||
efrom!(ZwpTextInputManagerV3Error, ClientError);
|
||||
320
src/ifs/wl_seat/text_input/zwp_text_input_v3.rs
Normal file
320
src/ifs/wl_seat/text_input/zwp_text_input_v3.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::{
|
||||
wl_seat::{
|
||||
text_input::{
|
||||
zwp_input_method_v2::ZwpInputMethodV2, TextConnectReason, TextDisconnectReason,
|
||||
TextInputConnection, MAX_TEXT_SIZE,
|
||||
},
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::Rect,
|
||||
utils::{clonecell::CloneCell, numcell::NumCell},
|
||||
wire::{zwp_text_input_v3::*, ZwpTextInputV3Id},
|
||||
},
|
||||
std::{cell::RefCell, collections::hash_map::Entry, mem, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpTextInputV3 {
|
||||
pub id: ZwpTextInputV3Id,
|
||||
pub client: Rc<Client>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
seat: Rc<WlSeatGlobal>,
|
||||
num_commits: NumCell<u32>,
|
||||
|
||||
state: RefCell<State>,
|
||||
pending: RefCell<Pending>,
|
||||
|
||||
pub connection: CloneCell<Option<Rc<TextInputConnection>>>,
|
||||
}
|
||||
|
||||
impl ZwpTextInputV3 {
|
||||
pub fn cursor_rect(&self) -> Rect {
|
||||
self.state.borrow().cursor_rectangle
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
id: ZwpTextInputV3Id,
|
||||
client: &Rc<Client>,
|
||||
seat: &Rc<WlSeatGlobal>,
|
||||
version: Version,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
client: client.clone(),
|
||||
tracker: Default::default(),
|
||||
version,
|
||||
seat: seat.clone(),
|
||||
num_commits: Default::default(),
|
||||
state: Default::default(),
|
||||
pending: Default::default(),
|
||||
connection: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn detach(&self) {
|
||||
self.do_disable();
|
||||
{
|
||||
let tis = &mut *self.seat.text_inputs.borrow_mut();
|
||||
if let Entry::Occupied(mut oe) = tis.entry(self.client.id) {
|
||||
oe.get_mut().remove(&self.id);
|
||||
if oe.get().is_empty() {
|
||||
oe.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_all_to(&self, im: &ZwpInputMethodV2) {
|
||||
let state = &*self.state.borrow();
|
||||
{
|
||||
let (a, b, c) = &state.surrounding_text;
|
||||
im.send_surrounding_text(a, *b, *c);
|
||||
}
|
||||
im.send_content_type(state.content_type.0, state.content_type.1);
|
||||
}
|
||||
|
||||
pub fn send_enter(&self, surface: &WlSurface) {
|
||||
self.client.event(Enter {
|
||||
self_id: self.id,
|
||||
surface: surface.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_leave(&self, surface: &WlSurface) {
|
||||
self.client.event(Leave {
|
||||
self_id: self.id,
|
||||
surface: surface.id,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_preedit_string(&self, text: Option<&str>, cursor_begin: i32, cursor_end: i32) {
|
||||
self.client.event(PreeditString {
|
||||
self_id: self.id,
|
||||
text,
|
||||
cursor_begin,
|
||||
cursor_end,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_commit_string(&self, text: Option<&str>) {
|
||||
self.client.event(CommitString {
|
||||
self_id: self.id,
|
||||
text,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_delete_surrounding_text(&self, before_length: u32, after_length: u32) {
|
||||
self.client.event(DeleteSurroundingText {
|
||||
self_id: self.id,
|
||||
before_length,
|
||||
after_length,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn send_done(&self) {
|
||||
self.client.event(Done {
|
||||
self_id: self.id,
|
||||
serial: self.num_commits.get(),
|
||||
});
|
||||
}
|
||||
|
||||
fn do_enable(self: &Rc<Self>) {
|
||||
if self.seat.text_input.is_some() {
|
||||
return;
|
||||
}
|
||||
let Some(surface) = self.seat.keyboard_node.get().node_into_surface() else {
|
||||
return;
|
||||
};
|
||||
if surface.client.id != self.client.id {
|
||||
return;
|
||||
}
|
||||
self.seat.text_input.set(Some(self.clone()));
|
||||
self.seat
|
||||
.create_text_input_connection(TextConnectReason::TextInputEnabled);
|
||||
}
|
||||
|
||||
fn do_disable(&self) {
|
||||
if let Some(con) = self.connection.take() {
|
||||
con.disconnect(TextDisconnectReason::TextInputDisabled);
|
||||
self.seat.text_input.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
enabled: bool,
|
||||
surrounding_text: (String, u32, u32),
|
||||
text_change_cause: u32,
|
||||
content_type: (u32, u32),
|
||||
cursor_rectangle: Rect,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Pending {
|
||||
enabled: Option<bool>,
|
||||
cursor_rect: Option<Rect>,
|
||||
content_type: Option<(u32, u32)>,
|
||||
text_change_cause: Option<u32>,
|
||||
surrounding_text: Option<(String, u32, u32)>,
|
||||
}
|
||||
|
||||
impl ZwpTextInputV3RequestHandler for ZwpTextInputV3 {
|
||||
type Error = ZwpTextInputV3Error;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enable(&self, _req: Enable, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.pending.borrow_mut().enabled = Some(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable(&self, _req: Disable, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.pending.borrow_mut().enabled = Some(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_surrounding_text(
|
||||
&self,
|
||||
req: SetSurroundingText<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
if req.text.len() > MAX_TEXT_SIZE {
|
||||
return Err(ZwpTextInputV3Error::TooLarge);
|
||||
}
|
||||
if !req.text.is_char_boundary(req.cursor as usize) {
|
||||
return Err(ZwpTextInputV3Error::CursorNotCharBoundary);
|
||||
}
|
||||
if !req.text.is_char_boundary(req.anchor as usize) {
|
||||
return Err(ZwpTextInputV3Error::AnchorNotCharBoundary);
|
||||
}
|
||||
self.pending.borrow_mut().surrounding_text =
|
||||
Some((req.text.to_string(), req.cursor as _, req.anchor as _));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_text_change_cause(
|
||||
&self,
|
||||
req: SetTextChangeCause,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.pending.borrow_mut().text_change_cause = Some(req.cause);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_content_type(&self, req: SetContentType, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.pending.borrow_mut().content_type = Some((req.hint, req.purpose));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_cursor_rectangle(
|
||||
&self,
|
||||
req: SetCursorRectangle,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let Some(rect) = Rect::new_sized(req.x, req.y, req.width, req.height) else {
|
||||
return Err(ZwpTextInputV3Error::InvalidRectangle);
|
||||
};
|
||||
self.pending.borrow_mut().cursor_rect = Some(rect);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn commit(&self, _req: Commit, slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.num_commits.fetch_add(1);
|
||||
let pending = self.pending.take();
|
||||
let state = &mut *self.state.borrow_mut();
|
||||
let mut sent_any = false;
|
||||
if let Some(val) = pending.enabled {
|
||||
sent_any = true;
|
||||
if val {
|
||||
mem::take(state);
|
||||
if let Some(con) = self.connection.get() {
|
||||
con.input_method.activate();
|
||||
} else {
|
||||
slf.do_enable();
|
||||
}
|
||||
} else {
|
||||
self.do_disable();
|
||||
}
|
||||
state.enabled = val;
|
||||
}
|
||||
let con = self.connection.get();
|
||||
if let Some(val) = pending.cursor_rect {
|
||||
if state.cursor_rectangle != val {
|
||||
if let Some(con) = &con {
|
||||
for (_, popup) in &con.input_method.popups {
|
||||
popup.schedule_positioning();
|
||||
}
|
||||
}
|
||||
}
|
||||
state.cursor_rectangle = val;
|
||||
}
|
||||
if let Some(val) = pending.content_type {
|
||||
if let Some(con) = &con {
|
||||
sent_any = true;
|
||||
con.input_method.send_content_type(val.0, val.1);
|
||||
}
|
||||
state.content_type = val;
|
||||
}
|
||||
if let Some(val) = pending.text_change_cause {
|
||||
if let Some(con) = &con {
|
||||
sent_any = true;
|
||||
con.input_method.send_text_change_cause(val);
|
||||
}
|
||||
state.text_change_cause = val;
|
||||
}
|
||||
if let Some(val) = pending.surrounding_text {
|
||||
if let Some(con) = &con {
|
||||
sent_any = true;
|
||||
con.input_method.send_surrounding_text(&val.0, val.1, val.2);
|
||||
}
|
||||
state.surrounding_text = val;
|
||||
}
|
||||
if sent_any {
|
||||
if let Some(con) = &con {
|
||||
con.input_method.send_done();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpTextInputV3;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpTextInputV3 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_obj!(ZwpTextInputV3);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpTextInputV3Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("Rectangle is invalid")]
|
||||
InvalidRectangle,
|
||||
#[error("The cursor is not at a char boundary")]
|
||||
CursorNotCharBoundary,
|
||||
#[error("The anchor is not at a char boundary")]
|
||||
AnchorNotCharBoundary,
|
||||
#[error("Text is larger than {} bytes", MAX_TEXT_SIZE)]
|
||||
TooLarge,
|
||||
}
|
||||
efrom!(ZwpTextInputV3Error, ClientError);
|
||||
|
|
@ -4,10 +4,11 @@ use {
|
|||
ifs::wl_seat::WlSeat,
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::{errorfmt::ErrorFmt, numcell::NumCell, oserror::OsError},
|
||||
utils::errorfmt::ErrorFmt,
|
||||
wire::{wl_keyboard::*, WlKeyboardId, WlSurfaceId},
|
||||
xkbcommon::{KeyboardState, KeyboardStateId, ModifierState, XkbCommonError},
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ pub const PRESSED: u32 = 1;
|
|||
pub struct WlKeyboard {
|
||||
id: WlKeyboardId,
|
||||
seat: Rc<WlSeat>,
|
||||
pub(super) keymap_version: NumCell<u32>,
|
||||
kb_state_id: Cell<KeyboardStateId>,
|
||||
pub tracker: Tracker<Self>,
|
||||
}
|
||||
|
||||
|
|
@ -32,14 +33,33 @@ impl WlKeyboard {
|
|||
Self {
|
||||
id,
|
||||
seat: seat.clone(),
|
||||
keymap_version: NumCell::new(0),
|
||||
kb_state_id: Cell::new(KeyboardStateId::from_raw(0)),
|
||||
tracker: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_keymap(&self) {
|
||||
let map = self.seat.global.effective_kb_map.get();
|
||||
let fd = match self.seat.keymap_fd(&map) {
|
||||
pub fn kb_state_id(&self) -> KeyboardStateId {
|
||||
self.kb_state_id.get()
|
||||
}
|
||||
|
||||
fn send_kb_state(
|
||||
&self,
|
||||
serial: u32,
|
||||
kb_state: &KeyboardState,
|
||||
surface_id: WlSurfaceId,
|
||||
send_leave: bool,
|
||||
) {
|
||||
self.kb_state_id.set(kb_state.id);
|
||||
if send_leave {
|
||||
self.send_leave(serial, surface_id);
|
||||
}
|
||||
self.send_keymap(kb_state);
|
||||
self.send_enter(serial, surface_id, &kb_state.pressed_keys);
|
||||
self.send_modifiers(serial, &kb_state.mods);
|
||||
}
|
||||
|
||||
fn send_keymap(&self, state: &KeyboardState) {
|
||||
let fd = match self.seat.keymap_fd(state) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
|
|
@ -54,16 +74,20 @@ impl WlKeyboard {
|
|||
self_id: self.id,
|
||||
format: XKB_V1,
|
||||
fd,
|
||||
size: map.map_len as _,
|
||||
size: state.map_len as _,
|
||||
});
|
||||
self.keymap_version
|
||||
.set(self.seat.global.keymap_version.get());
|
||||
}
|
||||
|
||||
pub fn send_enter(self: &Rc<Self>, serial: u32, surface: WlSurfaceId, keys: &[u32]) {
|
||||
if self.keymap_version.get() != self.seat.global.keymap_version.get() {
|
||||
self.send_keymap();
|
||||
pub fn enter(&self, serial: u32, surface: WlSurfaceId, kb_state: &KeyboardState) {
|
||||
if kb_state.id != self.kb_state_id.get() {
|
||||
self.send_kb_state(serial, kb_state, surface, false);
|
||||
} else {
|
||||
self.send_enter(serial, surface, &kb_state.pressed_keys);
|
||||
self.send_modifiers(serial, &kb_state.mods);
|
||||
}
|
||||
}
|
||||
|
||||
fn send_enter(&self, serial: u32, surface: WlSurfaceId, keys: &[u32]) {
|
||||
self.seat.client.event(Enter {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
|
|
@ -72,7 +96,7 @@ impl WlKeyboard {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn send_leave(self: &Rc<Self>, serial: u32, surface: WlSurfaceId) {
|
||||
pub fn send_leave(&self, serial: u32, surface: WlSurfaceId) {
|
||||
self.seat.client.event(Leave {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
|
|
@ -80,7 +104,22 @@ impl WlKeyboard {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn send_key(self: &Rc<Self>, serial: u32, time: u32, key: u32, state: u32) {
|
||||
pub fn on_key(
|
||||
&self,
|
||||
serial: u32,
|
||||
time: u32,
|
||||
key: u32,
|
||||
state: u32,
|
||||
surface: WlSurfaceId,
|
||||
kb_state: &KeyboardState,
|
||||
) {
|
||||
if self.kb_state_id.get() != kb_state.id {
|
||||
self.send_kb_state(serial, kb_state, surface, true);
|
||||
}
|
||||
self.send_key(serial, time, key, state);
|
||||
}
|
||||
|
||||
fn send_key(&self, serial: u32, time: u32, key: u32, state: u32) {
|
||||
self.seat.client.event(Key {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
|
|
@ -90,21 +129,22 @@ impl WlKeyboard {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn send_modifiers(
|
||||
self: &Rc<Self>,
|
||||
serial: u32,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) {
|
||||
pub fn on_mods_changed(&self, serial: u32, surface: WlSurfaceId, kb_state: &KeyboardState) {
|
||||
if self.kb_state_id.get() != kb_state.id {
|
||||
self.send_kb_state(serial, kb_state, surface, true);
|
||||
} else {
|
||||
self.send_modifiers(serial, &kb_state.mods);
|
||||
}
|
||||
}
|
||||
|
||||
fn send_modifiers(&self, serial: u32, mods: &ModifierState) {
|
||||
self.seat.client.event(Modifiers {
|
||||
self_id: self.id,
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
mods_depressed: mods.mods_depressed,
|
||||
mods_latched: mods.mods_latched,
|
||||
mods_locked: mods.mods_locked,
|
||||
group: mods.group,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -140,9 +180,7 @@ simple_add_obj!(WlKeyboard);
|
|||
pub enum WlKeyboardError {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error("Could not create a keymap memfd")]
|
||||
KeymapMemfd(#[source] OsError),
|
||||
#[error("Could not copy the keymap")]
|
||||
KeymapCopy(#[source] OsError),
|
||||
#[error(transparent)]
|
||||
XkbCommonError(#[from] XkbCommonError),
|
||||
}
|
||||
efrom!(WlKeyboardError, ClientError);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ use {
|
|||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
wire::{zwp_virtual_keyboard_manager_v1::*, ZwpVirtualKeyboardManagerV1Id},
|
||||
xkbcommon::KeyboardState,
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -76,14 +77,20 @@ impl ZwpVirtualKeyboardManagerV1RequestHandler for ZwpVirtualKeyboardManagerV1 {
|
|||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
let seat = self.client.lookup(req.seat)?;
|
||||
let seat_keymap = seat.global.seat_kb_map.get();
|
||||
let kb = Rc::new(ZwpVirtualKeyboardV1 {
|
||||
id: req.id,
|
||||
client: self.client.clone(),
|
||||
seat: seat.global.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
keymap_id: Default::default(),
|
||||
keymap: Default::default(),
|
||||
kb_state: Rc::new(RefCell::new(KeyboardState {
|
||||
id: self.client.state.keyboard_state_ids.next(),
|
||||
map: seat_keymap.map.clone(),
|
||||
map_len: seat_keymap.map_len,
|
||||
pressed_keys: Default::default(),
|
||||
mods: Default::default(),
|
||||
})),
|
||||
});
|
||||
track!(self.client, kb);
|
||||
self.client.add_client_obj(&kb)?;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::KeyState,
|
||||
client::{Client, ClientError},
|
||||
clientmem::{ClientMem, ClientMemError},
|
||||
ifs::wl_seat::{wl_keyboard, WlSeatGlobal},
|
||||
ifs::{
|
||||
wl_seat::{
|
||||
wl_keyboard::{self, WlKeyboard},
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
utils::clonecell::CloneCell,
|
||||
wire::{zwp_virtual_keyboard_v1::*, ZwpVirtualKeyboardV1Id},
|
||||
xkbcommon::{KeymapId, XkbCommonError, XkbKeymap},
|
||||
xkbcommon::{KeyboardState, XkbCommonError},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -20,21 +24,21 @@ pub struct ZwpVirtualKeyboardV1 {
|
|||
pub seat: Rc<WlSeatGlobal>,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub version: Version,
|
||||
pub keymap_id: Cell<Option<KeymapId>>,
|
||||
pub keymap: CloneCell<Option<Rc<XkbKeymap>>>,
|
||||
pub kb_state: Rc<RefCell<KeyboardState>>,
|
||||
}
|
||||
|
||||
impl ZwpVirtualKeyboardV1 {
|
||||
fn ensure_keymap(&self) {
|
||||
if let Some(id) = self.keymap_id.get() {
|
||||
if id == self.seat.effective_kb_map_id.get() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let Some(keymap) = self.keymap.get() else {
|
||||
fn for_each_kb<F>(&self, mut f: F)
|
||||
where
|
||||
F: FnMut(u32, &WlSurface, &WlKeyboard),
|
||||
{
|
||||
let Some(surface) = self.seat.keyboard_node.get().node_into_surface() else {
|
||||
return;
|
||||
};
|
||||
self.seat.set_effective_keymap(&keymap);
|
||||
let serial = surface.client.next_serial();
|
||||
self.seat.surface_kb_event(Version::ALL, &surface, |kb| {
|
||||
f(serial, &surface, kb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,31 +70,48 @@ impl ZwpVirtualKeyboardV1RequestHandler for ZwpVirtualKeyboardV1 {
|
|||
.xkb_ctx
|
||||
.keymap_from_str(&map)
|
||||
.map_err(ZwpVirtualKeyboardV1Error::ParseKeymap)?;
|
||||
self.keymap_id.set(Some(map.id));
|
||||
self.keymap.set(Some(map));
|
||||
*self.kb_state.borrow_mut() = KeyboardState {
|
||||
id: self.client.state.keyboard_state_ids.next(),
|
||||
map: map.map.clone(),
|
||||
map_len: map.map_len,
|
||||
pressed_keys: Default::default(),
|
||||
mods: Default::default(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key(&self, req: Key, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.ensure_keymap();
|
||||
let time_usec = (req.time as u64) * 1000;
|
||||
let state = match req.state {
|
||||
wl_keyboard::RELEASED => KeyState::Released,
|
||||
wl_keyboard::PRESSED => KeyState::Pressed,
|
||||
let kb_state = &mut *self.kb_state.borrow_mut();
|
||||
let contains = kb_state.pressed_keys.contains(&req.key);
|
||||
let valid = match req.state {
|
||||
wl_keyboard::RELEASED => contains,
|
||||
wl_keyboard::PRESSED => !contains,
|
||||
_ => return Err(ZwpVirtualKeyboardV1Error::UnknownState(req.state)),
|
||||
};
|
||||
self.seat.key_event(time_usec, req.key, state);
|
||||
if valid {
|
||||
self.for_each_kb(|serial, surface, kb| {
|
||||
kb.on_key(serial, req.time, req.key, req.state, surface.id, kb_state);
|
||||
});
|
||||
match req.state {
|
||||
wl_keyboard::RELEASED => kb_state.pressed_keys.remove(&req.key),
|
||||
_ => kb_state.pressed_keys.insert(req.key),
|
||||
};
|
||||
self.seat.latest_kb_state.set(self.kb_state.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn modifiers(&self, req: Modifiers, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.ensure_keymap();
|
||||
self.seat.set_modifiers(
|
||||
req.mods_depressed,
|
||||
req.mods_latched,
|
||||
req.mods_locked,
|
||||
req.group,
|
||||
);
|
||||
let kb_state = &mut *self.kb_state.borrow_mut();
|
||||
kb_state.mods.mods_depressed = req.mods_depressed;
|
||||
kb_state.mods.mods_latched = req.mods_latched;
|
||||
kb_state.mods.mods_locked = req.mods_locked;
|
||||
kb_state.mods.mods_effective = req.mods_depressed | req.mods_latched | req.mods_locked;
|
||||
kb_state.mods.group = req.group;
|
||||
self.for_each_kb(|serial, surface, kb| {
|
||||
kb.on_mods_changed(serial, surface.id, &kb_state);
|
||||
});
|
||||
self.seat.latest_kb_state.set(self.kb_state.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub mod xdg_surface;
|
|||
pub mod xwayland_shell_v1;
|
||||
pub mod zwlr_layer_surface_v1;
|
||||
pub mod zwp_idle_inhibitor_v1;
|
||||
pub mod zwp_input_popup_surface_v2;
|
||||
|
||||
use {
|
||||
crate::{
|
||||
|
|
@ -24,8 +25,9 @@ use {
|
|||
wl_buffer::WlBuffer,
|
||||
wl_callback::WlCallback,
|
||||
wl_seat::{
|
||||
wl_pointer::PendingScroll, zwp_pointer_constraints_v1::SeatConstraint, Dnd,
|
||||
NodeSeatState, SeatId, WlSeatGlobal,
|
||||
text_input::TextInputConnection, wl_pointer::PendingScroll,
|
||||
zwp_pointer_constraints_v1::SeatConstraint, Dnd, NodeSeatState, SeatId,
|
||||
WlSeatGlobal,
|
||||
},
|
||||
wl_surface::{
|
||||
commit_timeline::{ClearReason, CommitTimeline, CommitTimelineError},
|
||||
|
|
@ -65,7 +67,7 @@ use {
|
|||
wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id,
|
||||
ZwpLinuxDmabufFeedbackV1Id,
|
||||
},
|
||||
xkbcommon::ModifierState,
|
||||
xkbcommon::KeyboardState,
|
||||
xwayland::XWaylandEvent,
|
||||
},
|
||||
ahash::AHashMap,
|
||||
|
|
@ -104,6 +106,7 @@ pub enum SurfaceRole {
|
|||
ZwlrLayerSurface,
|
||||
XSurface,
|
||||
ExtSessionLockSurface,
|
||||
InputPopup,
|
||||
}
|
||||
|
||||
impl SurfaceRole {
|
||||
|
|
@ -117,6 +120,7 @@ impl SurfaceRole {
|
|||
SurfaceRole::ZwlrLayerSurface => "zwlr_layer_surface",
|
||||
SurfaceRole::XSurface => "xwayland surface",
|
||||
SurfaceRole::ExtSessionLockSurface => "ext_session_lock_surface",
|
||||
SurfaceRole::InputPopup => "input_popup_surface",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -249,6 +253,7 @@ pub struct WlSurface {
|
|||
commit_timeline: CommitTimeline,
|
||||
alpha_modifier: CloneCell<Option<Rc<WpAlphaModifierSurfaceV1>>>,
|
||||
alpha: Cell<Option<f32>>,
|
||||
pub text_input_connections: SmallMap<SeatId, Rc<TextInputConnection>, 1>,
|
||||
}
|
||||
|
||||
impl Debug for WlSurface {
|
||||
|
|
@ -533,6 +538,7 @@ impl WlSurface {
|
|||
commit_timeline: client.commit_timelines.create_timeline(),
|
||||
alpha_modifier: Default::default(),
|
||||
alpha: Default::default(),
|
||||
text_input_connections: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -604,6 +610,11 @@ impl WlSurface {
|
|||
.set_absolute_position(x1 + pos.x1(), y1 + pos.y1());
|
||||
}
|
||||
}
|
||||
for (_, con) in &self.text_input_connections {
|
||||
for (_, popup) in &con.input_method.popups {
|
||||
popup.schedule_positioning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_presentation_feedback(&self, fb: &Rc<WpPresentationFeedback>) {
|
||||
|
|
@ -1376,12 +1387,19 @@ impl Node for WlSurface {
|
|||
self.toplevel.get()
|
||||
}
|
||||
|
||||
fn node_on_key(&self, seat: &WlSeatGlobal, time_usec: u64, key: u32, state: u32) {
|
||||
seat.key_surface(self, time_usec, key, state);
|
||||
fn node_on_key(
|
||||
&self,
|
||||
seat: &WlSeatGlobal,
|
||||
time_usec: u64,
|
||||
key: u32,
|
||||
state: u32,
|
||||
kb_state: &KeyboardState,
|
||||
) {
|
||||
seat.key_surface(self, time_usec, key, state, kb_state);
|
||||
}
|
||||
|
||||
fn node_on_mods(&self, seat: &WlSeatGlobal, mods: ModifierState) {
|
||||
seat.mods_surface(self, mods);
|
||||
fn node_on_mods(&self, seat: &WlSeatGlobal, kb_state: &KeyboardState) {
|
||||
seat.mods_surface(self, kb_state);
|
||||
}
|
||||
|
||||
fn node_on_button(
|
||||
|
|
|
|||
174
src/ifs/wl_surface/zwp_input_popup_surface_v2.rs
Normal file
174
src/ifs/wl_surface/zwp_input_popup_surface_v2.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{Client, ClientError},
|
||||
ifs::{
|
||||
wl_seat::text_input::zwp_input_method_v2::ZwpInputMethodV2,
|
||||
wl_surface::{SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError},
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
rect::Rect,
|
||||
state::State,
|
||||
wire::{zwp_input_popup_surface_v2::*, WlSurfaceId, ZwpInputPopupSurfaceV2Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct ZwpInputPopupSurfaceV2 {
|
||||
pub id: ZwpInputPopupSurfaceV2Id,
|
||||
pub client: Rc<Client>,
|
||||
pub input_method: Rc<ZwpInputMethodV2>,
|
||||
pub surface: Rc<WlSurface>,
|
||||
pub version: Version,
|
||||
pub tracker: Tracker<Self>,
|
||||
pub positioning_scheduled: Cell<bool>,
|
||||
}
|
||||
|
||||
impl SurfaceExt for ZwpInputPopupSurfaceV2 {
|
||||
fn after_apply_commit(self: Rc<Self>) {
|
||||
self.update_visible();
|
||||
if self.surface.visible.get() {
|
||||
self.schedule_positioning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn input_popup_positioning(state: Rc<State>) {
|
||||
loop {
|
||||
let popup = state.pending_input_popup_positioning.pop().await;
|
||||
if popup.positioning_scheduled.get() {
|
||||
popup.position();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwpInputPopupSurfaceV2 {
|
||||
pub fn update_visible(self: &Rc<Self>) {
|
||||
let was_visible = self.surface.visible.get();
|
||||
let is_visible = self.surface.buffer.is_some()
|
||||
&& self.input_method.connection.is_some()
|
||||
&& self.client.state.root_visible();
|
||||
self.surface.set_visible(is_visible);
|
||||
if was_visible || is_visible {
|
||||
self.client.state.damage();
|
||||
}
|
||||
if !was_visible && is_visible {
|
||||
self.schedule_positioning();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_positioning(self: &Rc<Self>) {
|
||||
if self.surface.visible.get() {
|
||||
if !self.positioning_scheduled.replace(true) {
|
||||
self.client
|
||||
.state
|
||||
.pending_input_popup_positioning
|
||||
.push(self.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) {
|
||||
self.positioning_scheduled.set(false);
|
||||
if !self.surface.visible.get() {
|
||||
return;
|
||||
}
|
||||
let Some(con) = self.input_method.connection.get() else {
|
||||
log::warn!("Popup has no connection but is visible");
|
||||
return;
|
||||
};
|
||||
let output = con.surface.output.get().global.pos.get();
|
||||
let surface_rect = con.surface.buffer_abs_pos.get();
|
||||
let cursor_rect = con
|
||||
.text_input
|
||||
.cursor_rect()
|
||||
.move_(surface_rect.x1(), surface_rect.y1());
|
||||
let extents = self.surface.extents.get();
|
||||
let mut rect = extents.at_point(cursor_rect.x1(), cursor_rect.y2());
|
||||
let overflow = output.get_overflow(&rect);
|
||||
if overflow.right > 0 {
|
||||
let dx = -overflow.right.min(rect.width());
|
||||
let rect2 = rect.move_(dx, 0);
|
||||
if !output.get_overflow(&rect2).x_overflow() {
|
||||
rect = rect2;
|
||||
}
|
||||
}
|
||||
if overflow.bottom > 0 {
|
||||
let rect2 = rect.move_(0, -(cursor_rect.height() + rect.height()));
|
||||
if !output.get_overflow(&rect2).y_overflow() {
|
||||
rect = rect2;
|
||||
}
|
||||
}
|
||||
self.surface.buffer_abs_pos.set(
|
||||
self.surface
|
||||
.buffer_abs_pos
|
||||
.get()
|
||||
.at_point(rect.x1() - extents.x1(), rect.y1() - extents.y1()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn install(self: &Rc<Self>) -> Result<(), ZwpInputPopupSurfaceV2Error> {
|
||||
self.surface.set_role(SurfaceRole::InputPopup)?;
|
||||
if self.surface.ext.get().is_some() {
|
||||
return Err(ZwpInputPopupSurfaceV2Error::AlreadyAttached(
|
||||
self.surface.id,
|
||||
));
|
||||
}
|
||||
self.surface.ext.set(self.clone());
|
||||
self.input_method.popups.insert(self.id, self.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn send_text_input_rectangle(&self, rect: Rect) {
|
||||
self.client.event(TextInputRectangle {
|
||||
self_id: self.id,
|
||||
x: rect.x1(),
|
||||
y: rect.y1(),
|
||||
width: rect.width(),
|
||||
height: rect.height(),
|
||||
});
|
||||
}
|
||||
|
||||
fn detach(&self) {
|
||||
self.surface.destroy_node();
|
||||
self.surface.unset_ext();
|
||||
self.input_method.popups.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwpInputPopupSurfaceV2RequestHandler for ZwpInputPopupSurfaceV2 {
|
||||
type Error = ZwpInputPopupSurfaceV2Error;
|
||||
|
||||
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||
self.detach();
|
||||
self.client.remove_obj(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
self = ZwpInputPopupSurfaceV2;
|
||||
version = self.version;
|
||||
}
|
||||
|
||||
impl Object for ZwpInputPopupSurfaceV2 {
|
||||
fn break_loops(&self) {
|
||||
self.detach();
|
||||
}
|
||||
}
|
||||
|
||||
simple_add_obj!(ZwpInputPopupSurfaceV2);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ZwpInputPopupSurfaceV2Error {
|
||||
#[error(transparent)]
|
||||
ClientError(Box<ClientError>),
|
||||
#[error(transparent)]
|
||||
WlSurfaceError(Box<WlSurfaceError>),
|
||||
#[error("Surface {0} cannot be turned into a zwp_input_popup_surface_v2 because it already has an attached zwp_input_popup_surface_v2")]
|
||||
AlreadyAttached(WlSurfaceId),
|
||||
}
|
||||
efrom!(ZwpInputPopupSurfaceV2Error, WlSurfaceError);
|
||||
efrom!(ZwpInputPopupSurfaceV2Error, ClientError);
|
||||
|
|
@ -15,7 +15,7 @@ use {
|
|||
test_xdg_activation::TestXdgActivation, test_xdg_base::TestXdgWmBase,
|
||||
},
|
||||
test_transport::TestTransport,
|
||||
test_utils::test_window::TestWindow,
|
||||
test_utils::{test_surface_ext::TestSurfaceExt, test_window::TestWindow},
|
||||
testrun::TestRun,
|
||||
},
|
||||
theme::Color,
|
||||
|
|
@ -124,21 +124,24 @@ impl TestClient {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_window(&self) -> Result<Rc<TestWindow>, TestError> {
|
||||
pub async fn create_surface_ext(&self) -> Result<TestSurfaceExt, TestError> {
|
||||
let surface = self.comp.create_surface().await?;
|
||||
let viewport = self.viewporter.get_viewport(&surface)?;
|
||||
let xdg = self.xdg.create_xdg_surface(surface.id).await?;
|
||||
let tl = xdg.create_toplevel().await?;
|
||||
surface.commit()?;
|
||||
self.sync().await;
|
||||
Ok(Rc::new(TestWindow {
|
||||
Ok(TestSurfaceExt {
|
||||
surface,
|
||||
spbm: self.spbm.clone(),
|
||||
viewport,
|
||||
xdg,
|
||||
tl,
|
||||
color: Cell::new(Color::SOLID_BLACK),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_window(&self) -> Result<Rc<TestWindow>, TestError> {
|
||||
let surface = self.create_surface_ext().await?;
|
||||
let xdg = self.xdg.create_xdg_surface(surface.surface.id).await?;
|
||||
let tl = xdg.create_toplevel().await?;
|
||||
surface.surface.commit()?;
|
||||
self.sync().await;
|
||||
Ok(Rc::new(TestWindow { surface, xdg, tl }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ pub mod test_dmabuf;
|
|||
pub mod test_dmabuf_feedback;
|
||||
pub mod test_ext_foreign_toplevel_handle;
|
||||
pub mod test_ext_foreign_toplevel_list;
|
||||
pub mod test_input_method;
|
||||
pub mod test_input_method_keyboard_grab;
|
||||
pub mod test_input_method_manager;
|
||||
pub mod test_input_popup_surface;
|
||||
pub mod test_jay_compositor;
|
||||
pub mod test_keyboard;
|
||||
pub mod test_pointer;
|
||||
|
|
@ -37,6 +41,8 @@ pub mod test_surface;
|
|||
pub mod test_syncobj_manager;
|
||||
pub mod test_syncobj_surface;
|
||||
pub mod test_syncobj_timeline;
|
||||
pub mod test_text_input;
|
||||
pub mod test_text_input_manager;
|
||||
pub mod test_toplevel_drag;
|
||||
pub mod test_toplevel_drag_manager;
|
||||
pub mod test_viewport;
|
||||
|
|
|
|||
119
src/it/test_ifs/test_input_method.rs
Normal file
119
src/it/test_ifs/test_input_method.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::{TestError, TestResult},
|
||||
test_ifs::{
|
||||
test_input_method_keyboard_grab::TestInputMethodKeyboardGrab,
|
||||
test_input_popup_surface::TestInputPopupSurface, test_surface::TestSurface,
|
||||
},
|
||||
test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
test_utils::test_expected_event::TEEH,
|
||||
testrun::ParseFull,
|
||||
},
|
||||
utils::{buffd::MsgParser, numcell::NumCell},
|
||||
wire::{zwp_input_method_v2::*, ZwpInputMethodV2Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestInputMethod {
|
||||
pub id: ZwpInputMethodV2Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
pub destroyed: Cell<bool>,
|
||||
pub activate: TEEH<bool>,
|
||||
pub done: TEEH<()>,
|
||||
pub done_received: NumCell<u32>,
|
||||
}
|
||||
|
||||
impl TestInputMethod {
|
||||
pub fn commit_string(&self, s: &str) -> TestResult {
|
||||
self.tran.send(CommitString {
|
||||
self_id: self.id,
|
||||
text: s,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> TestResult {
|
||||
self.tran.send(Commit {
|
||||
self_id: self.id,
|
||||
serial: self.done_received.get(),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn grab(&self) -> TestResult<Rc<TestInputMethodKeyboardGrab>> {
|
||||
let obj = Rc::new(TestInputMethodKeyboardGrab {
|
||||
id: self.tran.id(),
|
||||
tran: self.tran.clone(),
|
||||
destroyed: Cell::new(false),
|
||||
keymap: Rc::new(Default::default()),
|
||||
key: Rc::new(Default::default()),
|
||||
modifiers: Rc::new(Default::default()),
|
||||
repeat_info: Rc::new(Default::default()),
|
||||
});
|
||||
self.tran.add_obj(obj.clone())?;
|
||||
self.tran.send(GrabKeyboard {
|
||||
self_id: self.id,
|
||||
keyboard: obj.id,
|
||||
})?;
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub fn get_popup(&self, surface: &TestSurface) -> TestResult<Rc<TestInputPopupSurface>> {
|
||||
let obj = Rc::new(TestInputPopupSurface {
|
||||
id: self.tran.id(),
|
||||
tran: self.tran.clone(),
|
||||
destroyed: Cell::new(false),
|
||||
});
|
||||
self.tran.add_obj(obj.clone())?;
|
||||
self.tran.send(GetInputPopupSurface {
|
||||
self_id: self.id,
|
||||
id: obj.id,
|
||||
surface: surface.id,
|
||||
})?;
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub fn destroy(&self) -> Result<(), TestError> {
|
||||
if !self.destroyed.replace(true) {
|
||||
self.tran.send(Destroy { self_id: self.id })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_activate(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let _ev = Activate::parse_full(parser)?;
|
||||
self.activate.push(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_deactivate(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let _ev = Deactivate::parse_full(parser)?;
|
||||
self.activate.push(false);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let _ev = Done::parse_full(parser)?;
|
||||
self.done.push(());
|
||||
self.done_received.fetch_add(1);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestInputMethod {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestInputMethod, ZwpInputMethodV2;
|
||||
|
||||
ACTIVATE => handle_activate,
|
||||
DEACTIVATE => handle_deactivate,
|
||||
DONE => handle_done,
|
||||
}
|
||||
|
||||
impl TestObject for TestInputMethod {}
|
||||
71
src/it/test_ifs/test_input_method_keyboard_grab.rs
Normal file
71
src/it/test_ifs/test_input_method_keyboard_grab.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::TestError, test_object::TestObject, test_transport::TestTransport,
|
||||
test_utils::test_expected_event::TEEH, testrun::ParseFull,
|
||||
},
|
||||
utils::buffd::MsgParser,
|
||||
wire::{zwp_input_method_keyboard_grab_v2::*, ZwpInputMethodKeyboardGrabV2Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestInputMethodKeyboardGrab {
|
||||
pub id: ZwpInputMethodKeyboardGrabV2Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
pub destroyed: Cell<bool>,
|
||||
pub keymap: TEEH<Keymap>,
|
||||
pub key: TEEH<Key>,
|
||||
pub modifiers: TEEH<Modifiers>,
|
||||
pub repeat_info: TEEH<RepeatInfo>,
|
||||
}
|
||||
|
||||
impl TestInputMethodKeyboardGrab {
|
||||
pub fn destroy(&self) -> Result<(), TestError> {
|
||||
if !self.destroyed.replace(true) {
|
||||
self.tran.send(Release { self_id: self.id })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_keymap(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Keymap::parse_full(parser)?;
|
||||
self.keymap.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Key::parse_full(parser)?;
|
||||
self.key.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_modifiers(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Modifiers::parse_full(parser)?;
|
||||
self.modifiers.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_repeat_info(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = RepeatInfo::parse_full(parser)?;
|
||||
self.repeat_info.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestInputMethodKeyboardGrab {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestInputMethodKeyboardGrab, ZwpInputMethodKeyboardGrabV2;
|
||||
|
||||
KEYMAP => handle_keymap,
|
||||
KEY => handle_key,
|
||||
MODIFIERS => handle_modifiers,
|
||||
REPEAT_INFO => handle_repeat_info,
|
||||
}
|
||||
|
||||
impl TestObject for TestInputMethodKeyboardGrab {}
|
||||
50
src/it/test_ifs/test_input_method_manager.rs
Normal file
50
src/it/test_ifs/test_input_method_manager.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::TestResult,
|
||||
test_ifs::{test_input_method::TestInputMethod, test_seat::TestSeat},
|
||||
test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
},
|
||||
wire::{zwp_input_method_manager_v2::GetInputMethod, ZwpInputMethodManagerV2Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestInputMethodManager {
|
||||
pub id: ZwpInputMethodManagerV2Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
}
|
||||
|
||||
impl TestInputMethodManager {
|
||||
pub fn new(tran: &Rc<TestTransport>) -> Self {
|
||||
Self {
|
||||
id: tran.id(),
|
||||
tran: tran.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_method(&self, seat: &TestSeat) -> TestResult<Rc<TestInputMethod>> {
|
||||
let obj = Rc::new(TestInputMethod {
|
||||
id: self.tran.id(),
|
||||
tran: self.tran.clone(),
|
||||
destroyed: Cell::new(false),
|
||||
activate: Rc::new(Default::default()),
|
||||
done: Rc::new(Default::default()),
|
||||
done_received: Default::default(),
|
||||
});
|
||||
self.tran.add_obj(obj.clone())?;
|
||||
self.tran.send(GetInputMethod {
|
||||
self_id: self.id,
|
||||
seat: seat.id,
|
||||
input_method: obj.id,
|
||||
})?;
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestInputMethodManager, ZwpInputMethodManagerV2;
|
||||
}
|
||||
|
||||
impl TestObject for TestInputMethodManager {}
|
||||
34
src/it/test_ifs/test_input_popup_surface.rs
Normal file
34
src/it/test_ifs/test_input_popup_surface.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{test_error::TestError, test_object::TestObject, test_transport::TestTransport},
|
||||
wire::{zwp_input_popup_surface_v2::*, ZwpInputPopupSurfaceV2Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestInputPopupSurface {
|
||||
pub id: ZwpInputPopupSurfaceV2Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
pub destroyed: Cell<bool>,
|
||||
}
|
||||
|
||||
impl TestInputPopupSurface {
|
||||
pub fn destroy(&self) -> Result<(), TestError> {
|
||||
if !self.destroyed.replace(true) {
|
||||
self.tran.send(Destroy { self_id: self.id })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestInputPopupSurface {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestInputPopupSurface, ZwpInputPopupSurfaceV2;
|
||||
}
|
||||
|
||||
impl TestObject for TestInputPopupSurface {}
|
||||
|
|
@ -11,9 +11,11 @@ use {
|
|||
test_data_control_manager::TestDataControlManager,
|
||||
test_data_device_manager::TestDataDeviceManager, test_dmabuf::TestDmabuf,
|
||||
test_ext_foreign_toplevel_list::TestExtForeignToplevelList,
|
||||
test_input_method_manager::TestInputMethodManager,
|
||||
test_jay_compositor::TestJayCompositor, test_shm::TestShm,
|
||||
test_single_pixel_buffer_manager::TestSinglePixelBufferManager,
|
||||
test_subcompositor::TestSubcompositor, test_syncobj_manager::TestSyncobjManager,
|
||||
test_text_input_manager::TestTextInputManager,
|
||||
test_toplevel_drag_manager::TestToplevelDragManager,
|
||||
test_viewporter::TestViewporter,
|
||||
test_virtual_keyboard_manager::TestVirtualKeyboardManager,
|
||||
|
|
@ -54,6 +56,8 @@ pub struct TestRegistrySingletons {
|
|||
pub xdg_toplevel_drag_manager_v1: u32,
|
||||
pub wp_alpha_modifier_v1: u32,
|
||||
pub zwp_virtual_keyboard_manager_v1: u32,
|
||||
pub zwp_input_method_manager_v2: u32,
|
||||
pub zwp_text_input_manager_v3: u32,
|
||||
}
|
||||
|
||||
pub struct TestRegistry {
|
||||
|
|
@ -79,6 +83,8 @@ pub struct TestRegistry {
|
|||
pub drag_manager: CloneCell<Option<Rc<TestToplevelDragManager>>>,
|
||||
pub alpha_modifier: CloneCell<Option<Rc<TestAlphaModifier>>>,
|
||||
pub virtual_keyboard_manager: CloneCell<Option<Rc<TestVirtualKeyboardManager>>>,
|
||||
pub input_method_manager: CloneCell<Option<Rc<TestInputMethodManager>>>,
|
||||
pub text_input_manager: CloneCell<Option<Rc<TestTextInputManager>>>,
|
||||
pub seats: CopyHashMap<GlobalName, Rc<WlSeatGlobal>>,
|
||||
}
|
||||
|
||||
|
|
@ -148,6 +154,8 @@ impl TestRegistry {
|
|||
xdg_toplevel_drag_manager_v1,
|
||||
wp_alpha_modifier_v1,
|
||||
zwp_virtual_keyboard_manager_v1,
|
||||
zwp_input_method_manager_v2,
|
||||
zwp_text_input_manager_v3,
|
||||
};
|
||||
self.singletons.set(Some(singletons.clone()));
|
||||
Ok(singletons)
|
||||
|
|
@ -249,6 +257,20 @@ impl TestRegistry {
|
|||
1,
|
||||
TestVirtualKeyboardManager
|
||||
);
|
||||
create_singleton!(
|
||||
get_input_method_manager,
|
||||
input_method_manager,
|
||||
zwp_input_method_manager_v2,
|
||||
1,
|
||||
TestInputMethodManager
|
||||
);
|
||||
create_singleton!(
|
||||
get_text_input_manager,
|
||||
text_input_manager,
|
||||
zwp_text_input_manager_v3,
|
||||
1,
|
||||
TestTextInputManager
|
||||
);
|
||||
|
||||
pub fn bind<O: TestObject>(
|
||||
&self,
|
||||
|
|
|
|||
97
src/it/test_ifs/test_text_input.rs
Normal file
97
src/it/test_ifs/test_text_input.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::{TestError, TestResult},
|
||||
test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
test_utils::test_expected_event::TEEH,
|
||||
testrun::ParseFull,
|
||||
},
|
||||
utils::buffd::MsgParser,
|
||||
wire::{zwp_text_input_v3::*, ZwpTextInputV3Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestTextInput {
|
||||
pub id: ZwpTextInputV3Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
pub destroyed: Cell<bool>,
|
||||
pub enter: TEEH<Enter>,
|
||||
pub leave: TEEH<Leave>,
|
||||
pub commit_string: TEEH<String>,
|
||||
pub done: TEEH<Done>,
|
||||
}
|
||||
|
||||
impl TestTextInput {
|
||||
pub fn destroy(&self) -> Result<(), TestError> {
|
||||
if !self.destroyed.replace(true) {
|
||||
self.tran.send(Destroy { self_id: self.id })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enable(&self) -> TestResult {
|
||||
self.tran.send(Enable { self_id: self.id })
|
||||
}
|
||||
|
||||
pub fn disable(&self) -> TestResult {
|
||||
self.tran.send(Disable { self_id: self.id })
|
||||
}
|
||||
|
||||
pub fn set_cursor_rectangle(&self, x: i32, y: i32, width: i32, height: i32) -> TestResult {
|
||||
self.tran.send(SetCursorRectangle {
|
||||
self_id: self.id,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> TestResult {
|
||||
self.tran.send(Commit { self_id: self.id })
|
||||
}
|
||||
|
||||
fn handle_enter(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Enter::parse_full(parser)?;
|
||||
self.enter.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_leave(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Leave::parse_full(parser)?;
|
||||
self.leave.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_commit_string(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = CommitString::parse_full(parser)?;
|
||||
self.commit_string
|
||||
.push(ev.text.unwrap_or_default().to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_done(&self, parser: MsgParser<'_, '_>) -> Result<(), TestError> {
|
||||
let ev = Done::parse_full(parser)?;
|
||||
self.done.push(ev);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestTextInput {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestTextInput, ZwpTextInputV3;
|
||||
|
||||
ENTER => handle_enter,
|
||||
LEAVE => handle_leave,
|
||||
COMMIT_STRING => handle_commit_string,
|
||||
DONE => handle_done,
|
||||
}
|
||||
|
||||
impl TestObject for TestTextInput {}
|
||||
51
src/it/test_ifs/test_text_input_manager.rs
Normal file
51
src/it/test_ifs/test_text_input_manager.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::TestResult,
|
||||
test_ifs::{test_seat::TestSeat, test_text_input::TestTextInput},
|
||||
test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
},
|
||||
wire::{zwp_text_input_manager_v3::*, ZwpTextInputManagerV3Id},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestTextInputManager {
|
||||
pub id: ZwpTextInputManagerV3Id,
|
||||
pub tran: Rc<TestTransport>,
|
||||
}
|
||||
|
||||
impl TestTextInputManager {
|
||||
pub fn new(tran: &Rc<TestTransport>) -> Self {
|
||||
Self {
|
||||
id: tran.id(),
|
||||
tran: tran.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text_input(&self, seat: &TestSeat) -> TestResult<Rc<TestTextInput>> {
|
||||
let obj = Rc::new(TestTextInput {
|
||||
id: self.tran.id(),
|
||||
tran: self.tran.clone(),
|
||||
destroyed: Cell::new(false),
|
||||
enter: Rc::new(Default::default()),
|
||||
leave: Rc::new(Default::default()),
|
||||
commit_string: Rc::new(Default::default()),
|
||||
done: Rc::new(Default::default()),
|
||||
});
|
||||
self.tran.add_obj(obj.clone())?;
|
||||
self.tran.send(GetTextInput {
|
||||
self_id: self.id,
|
||||
id: obj.id,
|
||||
seat: seat.id,
|
||||
})?;
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
test_object! {
|
||||
TestTextInputManager, ZwpTextInputManagerV3;
|
||||
}
|
||||
|
||||
impl TestObject for TestTextInputManager {}
|
||||
|
|
@ -70,6 +70,8 @@ impl TestTransport {
|
|||
drag_manager: Default::default(),
|
||||
alpha_modifier: Default::default(),
|
||||
virtual_keyboard_manager: Default::default(),
|
||||
input_method_manager: Default::default(),
|
||||
text_input_manager: Default::default(),
|
||||
seats: Default::default(),
|
||||
});
|
||||
self.send(wl_display::GetRegistry {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ pub mod test_expected_event;
|
|||
pub mod test_object_ext;
|
||||
pub mod test_ouput_node_ext;
|
||||
pub mod test_rect_ext;
|
||||
pub mod test_surface_ext;
|
||||
pub mod test_toplevel_node_ext;
|
||||
pub mod test_window;
|
||||
pub mod test_workspace_node_ext;
|
||||
|
|
|
|||
45
src/it/test_utils/test_surface_ext.rs
Normal file
45
src/it/test_utils/test_surface_ext.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::TestError,
|
||||
test_ifs::{
|
||||
test_single_pixel_buffer_manager::TestSinglePixelBufferManager,
|
||||
test_surface::TestSurface, test_viewport::TestViewport,
|
||||
},
|
||||
},
|
||||
theme::Color,
|
||||
},
|
||||
std::{cell::Cell, ops::Deref, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct TestSurfaceExt {
|
||||
pub surface: Rc<TestSurface>,
|
||||
pub spbm: Rc<TestSinglePixelBufferManager>,
|
||||
pub viewport: Rc<TestViewport>,
|
||||
pub color: Cell<Color>,
|
||||
}
|
||||
|
||||
impl Deref for TestSurfaceExt {
|
||||
type Target = TestSurface;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.surface
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSurfaceExt {
|
||||
pub async fn map(&self, width: i32, height: i32) -> Result<(), TestError> {
|
||||
let buffer = self.spbm.create_buffer(self.color.get())?;
|
||||
self.surface.attach(buffer.id)?;
|
||||
self.viewport.set_source(0, 0, 1, 1)?;
|
||||
self.viewport.set_destination(width, height)?;
|
||||
self.surface.commit()?;
|
||||
self.surface.tran.sync().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) {
|
||||
self.color.set(Color::from_rgba_straight(r, g, b, a));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,24 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_error::{TestError, TestResult},
|
||||
test_ifs::{
|
||||
test_single_pixel_buffer_manager::TestSinglePixelBufferManager,
|
||||
test_surface::TestSurface, test_viewport::TestViewport,
|
||||
test_xdg_surface::TestXdgSurface, test_xdg_toplevel::TestXdgToplevel,
|
||||
},
|
||||
},
|
||||
theme::Color,
|
||||
crate::it::{
|
||||
test_error::{TestError, TestResult},
|
||||
test_ifs::{test_xdg_surface::TestXdgSurface, test_xdg_toplevel::TestXdgToplevel},
|
||||
test_utils::test_surface_ext::TestSurfaceExt,
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct TestWindow {
|
||||
pub surface: Rc<TestSurface>,
|
||||
pub spbm: Rc<TestSinglePixelBufferManager>,
|
||||
pub viewport: Rc<TestViewport>,
|
||||
pub surface: TestSurfaceExt,
|
||||
pub xdg: Rc<TestXdgSurface>,
|
||||
pub tl: Rc<TestXdgToplevel>,
|
||||
pub color: Cell<Color>,
|
||||
}
|
||||
|
||||
impl TestWindow {
|
||||
pub async fn map(&self) -> Result<(), TestError> {
|
||||
let buffer = self.spbm.create_buffer(self.color.get())?;
|
||||
self.surface.attach(buffer.id)?;
|
||||
self.viewport.set_source(0, 0, 1, 1)?;
|
||||
self.viewport
|
||||
.set_destination(self.tl.core.width.get(), self.tl.core.height.get())?;
|
||||
self.xdg.ack_configure(self.xdg.last_serial.get())?;
|
||||
self.surface.commit()?;
|
||||
self.surface.tran.sync().await;
|
||||
self.surface
|
||||
.map(self.tl.core.width.get(), self.tl.core.height.get())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -40,8 +27,7 @@ impl TestWindow {
|
|||
self.map().await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) {
|
||||
self.color.set(Color::from_rgba_straight(r, g, b, a));
|
||||
self.surface.set_color(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ mod t0037_toplevel_drag;
|
|||
mod t0038_subsurface_parent_state;
|
||||
mod t0039_alpha_modifier;
|
||||
mod t0040_virtual_keyboard;
|
||||
mod t0041_input_method;
|
||||
|
||||
pub trait TestCase: Sync {
|
||||
fn name(&self) -> &'static str;
|
||||
|
|
@ -131,5 +132,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
|
|||
t0038_subsurface_parent_state,
|
||||
t0039_alpha_modifier,
|
||||
t0040_virtual_keyboard,
|
||||
t0041_input_method,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
mods.mods_locked,
|
||||
mods.group
|
||||
),
|
||||
(0, 0, 0, 0)
|
||||
(0, 0, 0, 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
136
src/it/tests/t0041_input_method.rs
Normal file
136
src/it/tests/t0041_input_method.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use {
|
||||
crate::{
|
||||
it::{
|
||||
test_client::{DefaultSeat, TestClient},
|
||||
test_error::TestResult,
|
||||
test_ifs::{
|
||||
test_input_method::TestInputMethod,
|
||||
test_input_popup_surface::TestInputPopupSurface, test_text_input::TestTextInput,
|
||||
},
|
||||
test_utils::{
|
||||
test_expected_event::TestExpectedEvent, test_surface_ext::TestSurfaceExt,
|
||||
test_window::TestWindow,
|
||||
},
|
||||
testrun::TestRun,
|
||||
},
|
||||
wire::zwp_text_input_v3,
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
testcase!();
|
||||
|
||||
async fn test(run: Rc<TestRun>) -> TestResult {
|
||||
let _ds = run.create_default_setup().await?;
|
||||
|
||||
let consumer = create_consumer(&run).await?;
|
||||
let supplier = create_supplier(&run).await?;
|
||||
|
||||
consumer.client.compare_screenshot("1", false).await?;
|
||||
|
||||
supplier.client.sync().await;
|
||||
tassert!(supplier.activate.next().is_err());
|
||||
|
||||
consumer.text.enable()?;
|
||||
consumer.text.set_cursor_rectangle(100, 100, 100, 100)?;
|
||||
consumer.text.commit()?;
|
||||
consumer.client.sync().await;
|
||||
|
||||
supplier.client.sync().await;
|
||||
tassert!(matches!(supplier.activate.next(), Ok(true)));
|
||||
tassert!(supplier.done.next().is_ok());
|
||||
|
||||
consumer.client.compare_screenshot("1", false).await?;
|
||||
|
||||
supplier.surface.commit()?;
|
||||
supplier.client.sync().await;
|
||||
|
||||
consumer.client.compare_screenshot("2", false).await?;
|
||||
|
||||
supplier.im.commit_string("hello world")?;
|
||||
supplier.im.commit()?;
|
||||
supplier.client.sync().await;
|
||||
|
||||
consumer.client.sync().await;
|
||||
tassert_eq!(
|
||||
consumer.commit_string.next().expect("commit string"),
|
||||
"hello world"
|
||||
);
|
||||
tassert!(consumer.done.next().is_ok());
|
||||
|
||||
consumer.text.disable()?;
|
||||
consumer.text.commit()?;
|
||||
consumer.client.sync().await;
|
||||
|
||||
consumer.client.compare_screenshot("3", false).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Consumer {
|
||||
client: Rc<TestClient>,
|
||||
_seat: DefaultSeat,
|
||||
_window: Rc<TestWindow>,
|
||||
text: Rc<TestTextInput>,
|
||||
_enter: TestExpectedEvent<zwp_text_input_v3::Enter>,
|
||||
_leave: TestExpectedEvent<zwp_text_input_v3::Leave>,
|
||||
commit_string: TestExpectedEvent<String>,
|
||||
done: TestExpectedEvent<zwp_text_input_v3::Done>,
|
||||
}
|
||||
|
||||
async fn create_consumer(run: &Rc<TestRun>) -> TestResult<Consumer> {
|
||||
let client = run.create_client().await?;
|
||||
let seat = client.get_default_seat().await?;
|
||||
let text = client
|
||||
.registry
|
||||
.get_text_input_manager()
|
||||
.await?
|
||||
.get_text_input(&seat.seat)?;
|
||||
let window = client.create_window().await?;
|
||||
window.map2().await?;
|
||||
client.sync().await;
|
||||
Ok(Consumer {
|
||||
_enter: text.enter.expect()?,
|
||||
_leave: text.leave.expect()?,
|
||||
commit_string: text.commit_string.expect()?,
|
||||
done: text.done.expect()?,
|
||||
client,
|
||||
_seat: seat,
|
||||
_window: window,
|
||||
text,
|
||||
})
|
||||
}
|
||||
|
||||
struct Supplier {
|
||||
client: Rc<TestClient>,
|
||||
_seat: DefaultSeat,
|
||||
im: Rc<TestInputMethod>,
|
||||
surface: TestSurfaceExt,
|
||||
_popup: Rc<TestInputPopupSurface>,
|
||||
activate: TestExpectedEvent<bool>,
|
||||
done: TestExpectedEvent<()>,
|
||||
}
|
||||
|
||||
async fn create_supplier(run: &Rc<TestRun>) -> TestResult<Supplier> {
|
||||
let client = run.create_client().await?;
|
||||
let seat = client.get_default_seat().await?;
|
||||
let im = client
|
||||
.registry
|
||||
.get_input_method_manager()
|
||||
.await?
|
||||
.get_input_method(&seat.seat)?;
|
||||
let surface = client.create_surface_ext().await?;
|
||||
surface.set_color(255, 0, 0, 255);
|
||||
surface.map(100, 100).await?;
|
||||
let popup = im.get_popup(&surface)?;
|
||||
client.sync().await;
|
||||
Ok(Supplier {
|
||||
activate: im.activate.expect()?,
|
||||
done: im.done.expect()?,
|
||||
client,
|
||||
_seat: seat,
|
||||
im,
|
||||
surface,
|
||||
_popup: popup,
|
||||
})
|
||||
}
|
||||
BIN
src/it/tests/t0041_input_method/screenshot_1.qoi
Normal file
BIN
src/it/tests/t0041_input_method/screenshot_1.qoi
Normal file
Binary file not shown.
BIN
src/it/tests/t0041_input_method/screenshot_2.qoi
Normal file
BIN
src/it/tests/t0041_input_method/screenshot_2.qoi
Normal file
Binary file not shown.
BIN
src/it/tests/t0041_input_method/screenshot_3.qoi
Normal file
BIN
src/it/tests/t0041_input_method/screenshot_3.qoi
Normal file
Binary file not shown.
|
|
@ -75,9 +75,10 @@ pub fn take_screenshot(
|
|||
fb.render_node(
|
||||
state.root.deref(),
|
||||
state,
|
||||
include_cursor.then_some(state.root.extents.get()),
|
||||
Some(state.root.extents.get()),
|
||||
None,
|
||||
Scale::from_int(1),
|
||||
include_cursor,
|
||||
true,
|
||||
false,
|
||||
Transform::None,
|
||||
|
|
|
|||
14
src/state.rs
14
src/state.rs
|
|
@ -36,6 +36,7 @@ use {
|
|||
wl_surface::{
|
||||
wl_subsurface::SubsurfaceIds,
|
||||
zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1},
|
||||
zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||
NoneSurfaceExt, WlSurface,
|
||||
},
|
||||
wp_linux_drm_syncobj_manager_v1::WpLinuxDrmSyncobjManagerV1Global,
|
||||
|
|
@ -74,7 +75,7 @@ use {
|
|||
ExtForeignToplevelListV1Id, JayRenderCtxId, JaySeatEventsId, JayWorkspaceWatcherId,
|
||||
ZwpLinuxDmabufFeedbackV1Id,
|
||||
},
|
||||
xkbcommon::{KeymapId, XkbContext, XkbKeymap},
|
||||
xkbcommon::{KeyboardStateIds, XkbContext, XkbKeymap, XkbState},
|
||||
xwayland::{self, XWaylandEvent},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
|
|
@ -134,6 +135,7 @@ pub struct State {
|
|||
pub pending_output_render_data: AsyncQueue<Rc<OutputNode>>,
|
||||
pub pending_float_layout: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_float_titles: AsyncQueue<Rc<FloatNode>>,
|
||||
pub pending_input_popup_positioning: AsyncQueue<Rc<ZwpInputPopupSurfaceV2>>,
|
||||
pub dbus: Dbus,
|
||||
pub fdcloser: Arc<FdCloser>,
|
||||
pub logger: Option<Arc<Logger>>,
|
||||
|
|
@ -175,6 +177,7 @@ pub struct State {
|
|||
pub subsurface_ids: SubsurfaceIds,
|
||||
pub wait_for_sync_obj: Rc<WaitForSyncObj>,
|
||||
pub explicit_sync_enabled: Cell<bool>,
|
||||
pub keyboard_state_ids: KeyboardStateIds,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -244,15 +247,8 @@ pub struct DeviceHandlerData {
|
|||
pub device: Rc<dyn InputDevice>,
|
||||
pub syspath: Option<String>,
|
||||
pub devnode: Option<String>,
|
||||
pub keymap_id: Cell<Option<KeymapId>>,
|
||||
pub keymap: CloneCell<Option<Rc<XkbKeymap>>>,
|
||||
}
|
||||
|
||||
impl DeviceHandlerData {
|
||||
pub fn set_keymap(&self, keymap: &Rc<XkbKeymap>) {
|
||||
self.keymap_id.set(Some(keymap.id));
|
||||
self.keymap.set(Some(keymap.clone()));
|
||||
}
|
||||
pub xkb_state: CloneCell<Option<Rc<RefCell<XkbState>>>>,
|
||||
}
|
||||
|
||||
pub struct ConnectorData {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ pub fn handle(state: &Rc<State>, dev: Rc<dyn InputDevice>) {
|
|||
device: dev.clone(),
|
||||
syspath: props.syspath,
|
||||
devnode: props.devnode,
|
||||
keymap_id: Default::default(),
|
||||
keymap: Default::default(),
|
||||
xkb_state: Default::default(),
|
||||
});
|
||||
let ae = Rc::new(AsyncEvent::default());
|
||||
let oh = DeviceHandler {
|
||||
|
|
@ -58,7 +58,7 @@ impl DeviceHandler {
|
|||
}
|
||||
for seat in self.state.globals.seats.lock().values() {
|
||||
if seat.seat_name() == DEFAULT_SEAT_NAME {
|
||||
self.data.seat.set(Some(seat.clone()));
|
||||
self.data.set_seat(Some(seat.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
src/tree.rs
16
src/tree.rs
|
|
@ -10,7 +10,7 @@ use {
|
|||
rect::Rect,
|
||||
renderer::Renderer,
|
||||
utils::numcell::NumCell,
|
||||
xkbcommon::ModifierState,
|
||||
xkbcommon::KeyboardState,
|
||||
},
|
||||
jay_config::Direction as JayDirection,
|
||||
std::{
|
||||
|
|
@ -158,16 +158,24 @@ pub trait Node: 'static {
|
|||
|
||||
// EVENT HANDLERS
|
||||
|
||||
fn node_on_key(&self, seat: &WlSeatGlobal, time_usec: u64, key: u32, state: u32) {
|
||||
fn node_on_key(
|
||||
&self,
|
||||
seat: &WlSeatGlobal,
|
||||
time_usec: u64,
|
||||
key: u32,
|
||||
state: u32,
|
||||
kb_state: &KeyboardState,
|
||||
) {
|
||||
let _ = seat;
|
||||
let _ = time_usec;
|
||||
let _ = key;
|
||||
let _ = state;
|
||||
let _ = kb_state;
|
||||
}
|
||||
|
||||
fn node_on_mods(&self, seat: &WlSeatGlobal, mods: ModifierState) {
|
||||
fn node_on_mods(&self, seat: &WlSeatGlobal, kb_state: &KeyboardState) {
|
||||
let _ = seat;
|
||||
let _ = mods;
|
||||
let _ = kb_state;
|
||||
}
|
||||
|
||||
fn node_on_button(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ impl<T> Deref for VecSet<T> {
|
|||
}
|
||||
|
||||
impl<T> VecSet<T> {
|
||||
#[allow(dead_code)]
|
||||
pub fn clear(&mut self) {
|
||||
self.vec.clear();
|
||||
}
|
||||
|
|
|
|||
125
src/xkbcommon.rs
125
src/xkbcommon.rs
|
|
@ -6,15 +6,21 @@ include!(concat!(env!("OUT_DIR"), "/xkbcommon_tys.rs"));
|
|||
|
||||
pub use consts::*;
|
||||
use {
|
||||
crate::utils::{
|
||||
errorfmt::ErrorFmt, oserror::OsError, ptr_ext::PtrExt, trim::AsciiTrim, vecset::VecSet,
|
||||
},
|
||||
bstr::{BStr, ByteSlice},
|
||||
isnt::std_1::primitive::IsntConstPtrExt,
|
||||
std::{ffi::CStr, io::Write, ops::Deref, ptr, rc::Rc},
|
||||
};
|
||||
|
||||
use {
|
||||
crate::utils::{ptr_ext::PtrExt, trim::AsciiTrim},
|
||||
std::{
|
||||
cell::{Ref, RefCell},
|
||||
ffi::CStr,
|
||||
io::Write,
|
||||
ops::Deref,
|
||||
ptr,
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::{c, OwnedFd},
|
||||
uapi::{c, Errno, OwnedFd},
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -27,6 +33,10 @@ pub enum XkbCommonError {
|
|||
KeymapFromBuffer,
|
||||
#[error("Could not convert the keymap to a string")]
|
||||
AsStr,
|
||||
#[error("Could not create a keymap memfd")]
|
||||
KeymapMemfd(#[source] OsError),
|
||||
#[error("Could not copy the keymap")]
|
||||
KeymapCopy(#[source] OsError),
|
||||
}
|
||||
|
||||
struct xkb_context;
|
||||
|
|
@ -202,7 +212,7 @@ pub struct XkbKeymap {
|
|||
}
|
||||
|
||||
impl XkbKeymap {
|
||||
pub fn state(self: &Rc<Self>) -> Result<XkbState, XkbCommonError> {
|
||||
pub fn state(self: &Rc<Self>, id: KeyboardStateId) -> Result<XkbState, XkbCommonError> {
|
||||
let res = unsafe { xkb_state_new(self.keymap) };
|
||||
if res.is_null() {
|
||||
return Err(XkbCommonError::CreateState);
|
||||
|
|
@ -210,12 +220,12 @@ impl XkbKeymap {
|
|||
Ok(XkbState {
|
||||
map: self.clone(),
|
||||
state: res,
|
||||
mods: ModifierState {
|
||||
mods_depressed: 0,
|
||||
mods_latched: 0,
|
||||
mods_locked: 0,
|
||||
mods_effective: 0,
|
||||
group: 0,
|
||||
kb_state: KeyboardState {
|
||||
id,
|
||||
map: self.map.clone(),
|
||||
map_len: self.map_len,
|
||||
pressed_keys: Default::default(),
|
||||
mods: Default::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -247,7 +257,7 @@ impl Drop for XkbKeymapStr {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ModifierState {
|
||||
pub mods_depressed: u32,
|
||||
pub mods_latched: u32,
|
||||
|
|
@ -256,51 +266,110 @@ pub struct ModifierState {
|
|||
pub group: u32,
|
||||
}
|
||||
|
||||
linear_ids!(KeyboardStateIds, KeyboardStateId, u64);
|
||||
|
||||
pub struct KeyboardState {
|
||||
pub id: KeyboardStateId,
|
||||
pub map: Rc<OwnedFd>,
|
||||
pub map_len: usize,
|
||||
pub pressed_keys: VecSet<u32>,
|
||||
pub mods: ModifierState,
|
||||
}
|
||||
|
||||
pub trait DynKeyboardState {
|
||||
fn borrow(&self) -> Ref<'_, KeyboardState>;
|
||||
}
|
||||
|
||||
impl DynKeyboardState for RefCell<KeyboardState> {
|
||||
fn borrow(&self) -> Ref<'_, KeyboardState> {
|
||||
self.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct XkbState {
|
||||
map: Rc<XkbKeymap>,
|
||||
state: *mut xkb_state,
|
||||
mods: ModifierState,
|
||||
pub kb_state: KeyboardState,
|
||||
}
|
||||
|
||||
impl DynKeyboardState for RefCell<XkbState> {
|
||||
fn borrow(&self) -> Ref<'_, KeyboardState> {
|
||||
Ref::map(self.borrow(), |v| &v.kb_state)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn create_new_keymap_fd(&self) -> Result<Rc<OwnedFd>, XkbCommonError> {
|
||||
let fd = match uapi::memfd_create("shared-keymap", c::MFD_CLOEXEC) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => return Err(XkbCommonError::KeymapMemfd(e.into())),
|
||||
};
|
||||
let target = self.map_len as c::off_t;
|
||||
let mut pos = 0;
|
||||
while pos < target {
|
||||
let rem = target - pos;
|
||||
let res = uapi::sendfile(fd.raw(), self.map.raw(), Some(&mut pos), rem as usize);
|
||||
match res {
|
||||
Ok(_) | Err(Errno(c::EINTR)) => {}
|
||||
Err(e) => return Err(XkbCommonError::KeymapCopy(e.into())),
|
||||
}
|
||||
}
|
||||
Ok(Rc::new(fd))
|
||||
}
|
||||
}
|
||||
|
||||
impl XkbState {
|
||||
pub fn mods(&self) -> ModifierState {
|
||||
self.mods
|
||||
self.kb_state.mods
|
||||
}
|
||||
|
||||
fn fetch(&mut self, changes: xkb_state_component) -> Option<ModifierState> {
|
||||
fn fetch(&mut self, changes: xkb_state_component) -> bool {
|
||||
unsafe {
|
||||
if changes != 0 {
|
||||
self.mods.mods_depressed =
|
||||
self.kb_state.mods.mods_depressed =
|
||||
xkb_state_serialize_mods(self.state, XKB_STATE_MODS_DEPRESSED.raw() as _);
|
||||
self.mods.mods_latched =
|
||||
self.kb_state.mods.mods_latched =
|
||||
xkb_state_serialize_mods(self.state, XKB_STATE_MODS_LATCHED.raw() as _);
|
||||
self.mods.mods_locked =
|
||||
self.kb_state.mods.mods_locked =
|
||||
xkb_state_serialize_mods(self.state, XKB_STATE_MODS_LOCKED.raw() as _);
|
||||
self.mods.mods_effective =
|
||||
self.mods.mods_depressed | self.mods.mods_latched | self.mods.mods_locked;
|
||||
self.mods.group =
|
||||
self.kb_state.mods.mods_effective = self.kb_state.mods.mods_depressed
|
||||
| self.kb_state.mods.mods_latched
|
||||
| self.kb_state.mods.mods_locked;
|
||||
self.kb_state.mods.group =
|
||||
xkb_state_serialize_layout(self.state, XKB_STATE_LAYOUT_EFFECTIVE.raw() as _);
|
||||
Some(self.mods)
|
||||
true
|
||||
} else {
|
||||
None
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, key: u32, direction: XkbKeyDirection) -> Option<ModifierState> {
|
||||
pub fn update(&mut self, key: u32, direction: XkbKeyDirection) -> bool {
|
||||
unsafe {
|
||||
let changes = xkb_state_update_key(self.state, key + 8, direction.raw() as _);
|
||||
self.fetch(changes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
let new_state = match self.map.state(self.kb_state.id) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("Could not reset XKB state: {}", ErrorFmt(e));
|
||||
return;
|
||||
}
|
||||
};
|
||||
*self = new_state;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) -> Option<ModifierState> {
|
||||
) -> bool {
|
||||
unsafe {
|
||||
let changes = xkb_state_update_mask(
|
||||
self.state,
|
||||
|
|
@ -321,7 +390,7 @@ impl XkbState {
|
|||
let num = xkb_keymap_key_get_syms_by_level(
|
||||
self.map.keymap,
|
||||
key + 8,
|
||||
self.mods.group,
|
||||
self.kb_state.mods.group,
|
||||
0,
|
||||
&mut res,
|
||||
);
|
||||
|
|
|
|||
29
wire/zwp_input_method_keyboard_grab_v2.txt
Normal file
29
wire/zwp_input_method_keyboard_grab_v2.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
request release {
|
||||
|
||||
}
|
||||
|
||||
event keymap {
|
||||
format: u32,
|
||||
fd: fd,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
event key {
|
||||
serial: u32,
|
||||
time: u32,
|
||||
key: u32,
|
||||
state: u32,
|
||||
}
|
||||
|
||||
event modifiers {
|
||||
serial: u32,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
}
|
||||
|
||||
event repeat_info {
|
||||
rate: i32,
|
||||
delay: i32,
|
||||
}
|
||||
8
wire/zwp_input_method_manager_v2.txt
Normal file
8
wire/zwp_input_method_manager_v2.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
request get_input_method {
|
||||
seat: id(wl_seat),
|
||||
input_method: id(zwp_input_method_v2),
|
||||
}
|
||||
|
||||
request destroy {
|
||||
|
||||
}
|
||||
62
wire/zwp_input_method_v2.txt
Normal file
62
wire/zwp_input_method_v2.txt
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
request commit_string {
|
||||
text: str,
|
||||
}
|
||||
|
||||
request set_preedit_string {
|
||||
text: str,
|
||||
cursor_begin: i32,
|
||||
cursor_end: i32,
|
||||
}
|
||||
|
||||
request delete_surrounding_text {
|
||||
before_length: u32,
|
||||
after_length: u32,
|
||||
}
|
||||
|
||||
request commit {
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
request get_input_popup_surface {
|
||||
id: id(zwp_input_popup_surface_v2),
|
||||
surface: id(wl_surface),
|
||||
}
|
||||
|
||||
request grab_keyboard {
|
||||
keyboard: id(zwp_input_method_keyboard_grab_v2),
|
||||
}
|
||||
|
||||
request destroy {
|
||||
|
||||
}
|
||||
|
||||
event activate {
|
||||
|
||||
}
|
||||
|
||||
event deactivate {
|
||||
|
||||
}
|
||||
|
||||
event surrounding_text {
|
||||
text: str,
|
||||
cursor: u32,
|
||||
anchor: u32,
|
||||
}
|
||||
|
||||
event text_change_cause {
|
||||
cause: u32,
|
||||
}
|
||||
|
||||
event content_type {
|
||||
hint: u32,
|
||||
purpose: u32,
|
||||
}
|
||||
|
||||
event done {
|
||||
|
||||
}
|
||||
|
||||
event unavailable {
|
||||
|
||||
}
|
||||
10
wire/zwp_input_popup_surface_v2.txt
Normal file
10
wire/zwp_input_popup_surface_v2.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
request destroy {
|
||||
|
||||
}
|
||||
|
||||
event text_input_rectangle {
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
7
wire/zwp_text_input_manager_v3.txt
Normal file
7
wire/zwp_text_input_manager_v3.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
request destroy {
|
||||
}
|
||||
|
||||
request get_text_input {
|
||||
id: id(zwp_text_input_v3),
|
||||
seat: id(wl_seat),
|
||||
}
|
||||
60
wire/zwp_text_input_v3.txt
Normal file
60
wire/zwp_text_input_v3.txt
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
request destroy {
|
||||
}
|
||||
|
||||
request enable {
|
||||
}
|
||||
|
||||
request disable {
|
||||
}
|
||||
|
||||
request set_surrounding_text {
|
||||
text: str,
|
||||
cursor: i32,
|
||||
anchor: i32,
|
||||
}
|
||||
|
||||
request set_text_change_cause {
|
||||
cause: u32,
|
||||
}
|
||||
|
||||
request set_content_type {
|
||||
hint: u32,
|
||||
purpose: u32,
|
||||
}
|
||||
|
||||
request set_cursor_rectangle {
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
request commit {
|
||||
}
|
||||
|
||||
event enter {
|
||||
surface: id(wl_surface),
|
||||
}
|
||||
|
||||
event leave {
|
||||
surface: id(wl_surface),
|
||||
}
|
||||
|
||||
event preedit_string {
|
||||
text: optstr,
|
||||
cursor_begin: i32,
|
||||
cursor_end: i32,
|
||||
}
|
||||
|
||||
event commit_string {
|
||||
text: optstr,
|
||||
}
|
||||
|
||||
event delete_surrounding_text {
|
||||
before_length: u32,
|
||||
after_length: u32,
|
||||
}
|
||||
|
||||
event done {
|
||||
serial: u32,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue