1
0
Fork 0
forked from wry/wry

seat: implement input methods

This commit is contained in:
Julian Orth 2024-04-14 20:22:57 +02:00
parent 5e2cdef388
commit daf52299db
44 changed files with 2165 additions and 75 deletions

View file

@ -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 | |

View file

@ -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)

View file

@ -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(),

View file

@ -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(),
@ -327,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())),
]
}

View file

@ -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,

View file

@ -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>) {

View file

@ -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,
@ -63,7 +68,7 @@ use {
wire::{
wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId,
WlSeatId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id,
ZwpRelativePointerV1Id,
ZwpRelativePointerV1Id, ZwpTextInputV3Id,
},
xkbcommon::{DynKeyboardState, KeyboardState, KeymapId, XkbKeymap, XkbState},
},
@ -78,7 +83,7 @@ use {
rc::{Rc, Weak},
},
thiserror::Error,
uapi::{c, Errno, OwnedFd},
uapi::OwnedFd,
};
pub const POINTER: u32 = 1;
@ -166,6 +171,10 @@ pub struct WlSeatGlobal {
constraint: CloneCell<Option<Rc<SeatConstraint>>>,
idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc<ExtIdleNotificationV1>>,
last_input_usec: Cell<u64>,
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;
@ -230,6 +239,10 @@ impl WlSeatGlobal {
idle_notifications: Default::default(),
last_input_usec: Cell::new(now_usec()),
wlr_data_devices: Default::default(),
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();
@ -250,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()
}
@ -731,6 +748,9 @@ impl WlSeatGlobal {
}
}
}
if let Some(grab) = self.input_method_grab.get() {
grab.send_repeat_info();
}
}
pub fn close(self: &Rc<Self>) {
@ -1048,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 {
@ -1116,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();
}
}
}
}
@ -1175,21 +1204,7 @@ impl WlSeat {
if self.version >= READ_ONLY_KEYMAP_SINCE {
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 = state.map_len as c::off_t;
let mut pos = 0;
while pos < target {
let rem = target - pos;
let res = uapi::sendfile(fd.raw(), state.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()?)
}
}

View file

@ -14,6 +14,7 @@ use {
DynDataSource,
},
wl_seat::{
text_input::TextDisconnectReason,
wl_keyboard::{self, WlKeyboard},
wl_pointer::{
self, PendingScroll, WlPointer, AXIS_DISCRETE_SINCE_VERSION,
@ -393,8 +394,12 @@ impl WlSeatGlobal {
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, &xkb_state.kb_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);
@ -411,7 +416,10 @@ impl WlSeatGlobal {
self.state.for_each_seat_tester(|t| {
t.send_modifiers(self.id, &xkb_state.kb_state.mods);
});
node.node_on_mods(self, &xkb_state.kb_state);
match &input_method_grab {
Some(g) => g.on_modifiers(&xkb_state.kb_state),
_ => node.node_on_mods(self, &xkb_state.kb_state),
}
}
match key_state {
KeyState::Released => {
@ -760,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))
}
@ -785,6 +805,13 @@ 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();
}
}
}
}

View 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();
}
}
}
}

View 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);

View 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);

View 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);

View 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);

View 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);

View file

@ -4,9 +4,9 @@ use {
ifs::wl_seat::WlSeat,
leaks::Tracker,
object::{Object, Version},
utils::{errorfmt::ErrorFmt, oserror::OsError},
utils::errorfmt::ErrorFmt,
wire::{wl_keyboard::*, WlKeyboardId, WlSurfaceId},
xkbcommon::{KeyboardState, KeyboardStateId, ModifierState},
xkbcommon::{KeyboardState, KeyboardStateId, ModifierState, XkbCommonError},
},
std::{cell::Cell, rc::Rc},
thiserror::Error,
@ -180,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);

View file

@ -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},
@ -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>) {

View 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);

View file

@ -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 }))
}
}

View file

@ -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;

View 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 {}

View 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 {}

View 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 {}

View 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 {}

View file

@ -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,

View 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 {}

View 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 {}

View file

@ -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 {

View file

@ -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;

View 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));
}
}

View file

@ -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);
}
}

View file

@ -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,
}
}

View 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,
})
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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,

View file

@ -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,
@ -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>>,

View file

@ -6,6 +6,9 @@ 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::{
@ -16,12 +19,8 @@ use {
ptr,
rc::Rc,
},
};
use {
crate::utils::{errorfmt::ErrorFmt, ptr_ext::PtrExt, trim::AsciiTrim, vecset::VecSet},
thiserror::Error,
uapi::{c, OwnedFd},
uapi::{c, Errno, OwnedFd},
};
#[derive(Debug, Error)]
@ -34,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;
@ -295,6 +298,26 @@ impl DynKeyboardState for RefCell<XkbState> {
}
}
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.kb_state.mods

View 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,
}

View file

@ -0,0 +1,8 @@
request get_input_method {
seat: id(wl_seat),
input_method: id(zwp_input_method_v2),
}
request destroy {
}

View 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 {
}

View file

@ -0,0 +1,10 @@
request destroy {
}
event text_input_rectangle {
x: i32,
y: i32,
width: i32,
height: i32,
}

View file

@ -0,0 +1,7 @@
request destroy {
}
request get_text_input {
id: id(zwp_text_input_v3),
seat: id(wl_seat),
}

View 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,
}