Skip to content

Commit 6f8ada6

Browse files
dczunset
authored andcommitted
input-method-experimental: Implement actions
Upstream protocol merged in https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/433
1 parent 2aee9a3 commit 6f8ada6

File tree

2 files changed

+108
-31
lines changed

2 files changed

+108
-31
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,11 @@ wayland-backend = "0.3.0"
2929
wayland-client = "0.31.1"
3030
wayland-cursor = "0.31.0"
3131
wayland-protocols = { version = "0.32.1", features = ["client", "staging", "unstable"] }
32-
wayland-protocols-experimental = { version = "20250721.0.1", features = ["client"] }
32+
wayland-protocols-experimental = { version = "20251230.0.1", features = ["client"] }
3333
wayland-protocols-misc = { version = "0.3.6", features = ["client"] }
3434
wayland-protocols-wlr = { version = "0.3.1", features = ["client"] }
3535
wayland-scanner = "0.31.0"
3636
wayland-csd-frame = "0.3.0"
37-
3837
xkbcommon = { version = "0.8.0", optional = true, features = ["wayland"] }
3938
xkeysym = "0.2.0"
4039

src/seat/input_method_v3.rs

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
11
/*! This implements support for the experimental xx-input-method-v2 protocol.
22
* That protocol will hopefully become -v3 without changing the API at some point.
3+
*
4+
*
5+
* This is a low-level interface to the input method. It will generally not check if the client is allowed to issue a request in context, e.g. when the input method is inactive.
6+
*
7+
* It does handle some serials for the client, as well as it checks the validity of values for the current protocol version.
8+
*
9+
* The client is responsible for avoiding protocol errors.
310
*/
411

512
use crate::compositor::Surface;
613
use crate::globals::GlobalData;
714

815
use log::{debug, warn};
916

10-
use std::collections::HashMap;
17+
use std::collections::{HashMap, HashSet};
1118
use std::num::Wrapping;
1219
use std::ops::Deref;
1320
use std::sync::{Arc, Mutex, MutexGuard, Weak};
1421

22+
use crate::reexports::protocols_experimental::text_input::v3::client::xx_text_input_v3::{
23+
Action, ChangeCause, ContentHint, ContentPurpose, SupportedFeatures,
24+
};
1525
use wayland_client::globals::{BindError, GlobalList};
1626
use wayland_client::protocol::wl_seat::WlSeat;
1727
use wayland_client::protocol::wl_surface;
1828
use wayland_client::WEnum;
1929
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
20-
use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
21-
ChangeCause, ContentHint, ContentPurpose,
22-
};
2330

24-
use wayland_protocols_experimental::input_method::v1::client as protocol;
31+
use crate::reexports::protocols_experimental::input_method::v1::client as protocol;
2532

2633
pub use protocol::xx_input_method_v1::XxInputMethodV1;
2734
pub use protocol::xx_input_popup_positioner_v1::XxInputPopupPositionerV1;
@@ -59,7 +66,7 @@ impl InputMethodManager {
5966
where
6067
D: Dispatch<XxInputMethodManagerV2, GlobalData> + 'static,
6168
{
62-
let manager = globals.bind(qh, 2..=2, GlobalData)?;
69+
let manager = globals.bind(qh, 2..=3, GlobalData)?;
6370
Ok(Self { manager })
6471
}
6572

@@ -210,6 +217,15 @@ impl InputMethod {
210217
self.input_method.delete_surrounding_text(before_length, after_length)
211218
}
212219

220+
/// This method doesn't check if the action has been made available for this text input.
221+
pub fn perform_action(&self, action: Action) {
222+
self.input_method.perform_action(action)
223+
}
224+
225+
pub fn move_cursor(&self, cursor: i32, anchor: i32) {
226+
self.input_method.move_cursor(cursor, anchor)
227+
}
228+
213229
pub fn commit(&self) {
214230
let data = self.input_method.data::<InputMethodData>().unwrap();
215231
let inner = &data.inner.lock().unwrap();
@@ -341,36 +357,54 @@ pub struct SurroundingText {
341357
pub anchor: u32,
342358
}
343359

360+
/// Describes operations that can be performed on this input method.
361+
#[non_exhaustive]
362+
// non exhaustive so that bumping protocol version and adding new ones
363+
// doesn't automatically break compat
364+
#[derive(Clone, Debug, PartialEq)]
365+
pub struct Capabilities {
366+
pub surrounding_text: bool,
367+
pub content_type: bool,
368+
pub actions: HashSet<Action>,
369+
pub supported_features: SupportedFeatures,
370+
}
371+
372+
impl Default for Capabilities {
373+
fn default() -> Self {
374+
Self {
375+
surrounding_text: false,
376+
content_type: false,
377+
actions: Default::default(),
378+
supported_features: SupportedFeatures::empty(),
379+
}
380+
}
381+
}
382+
344383
/// State machine for determining the capabilities of a text input
345-
#[derive(Clone, Debug, Default, Copy, PartialEq)]
384+
#[derive(Clone, Debug, Default, PartialEq)]
346385
pub enum Active {
347386
#[default]
348387
Inactive,
349-
NegotiatingCapabilities {
350-
surrounding_text: bool,
351-
content_type: bool,
352-
},
353-
Active {
354-
surrounding_text: bool,
355-
content_type: bool,
356-
},
388+
NegotiatingCapabilities(Capabilities),
389+
Active(Capabilities),
357390
}
358391

359392
impl Active {
360393
fn with_active(self) -> Self {
361394
match self {
362-
Self::Inactive => {
363-
Self::NegotiatingCapabilities { content_type: false, surrounding_text: false }
364-
}
395+
Self::Inactive => Self::NegotiatingCapabilities(Capabilities::default()),
365396
other => other,
366397
}
367398
}
368399

369400
fn with_surrounding_text(self) -> Self {
370401
match self {
371402
Self::Inactive => Self::Inactive,
372-
Self::NegotiatingCapabilities { content_type, .. } => {
373-
Self::NegotiatingCapabilities { content_type, surrounding_text: true }
403+
Self::NegotiatingCapabilities(capabilities) => {
404+
Self::NegotiatingCapabilities(Capabilities {
405+
surrounding_text: true,
406+
..capabilities
407+
})
374408
}
375409
active @ Self::Active { .. } => active,
376410
}
@@ -379,22 +413,40 @@ impl Active {
379413
fn with_content_type(self) -> Self {
380414
match self {
381415
Self::Inactive => Self::Inactive,
382-
Self::NegotiatingCapabilities { surrounding_text, .. } => {
383-
Self::NegotiatingCapabilities { content_type: true, surrounding_text }
416+
Self::NegotiatingCapabilities(capabilities) => {
417+
Self::NegotiatingCapabilities(Capabilities { content_type: true, ..capabilities })
384418
}
385419
active @ Self::Active { .. } => active,
386420
}
387421
}
388422

389-
fn with_done(self) -> Self {
423+
fn with_actions(self, actions: HashSet<Action>) -> Self {
424+
match self {
425+
Self::Inactive => Self::Inactive,
426+
Self::NegotiatingCapabilities(capabilities) => {
427+
Self::NegotiatingCapabilities(Capabilities { actions, ..capabilities })
428+
}
429+
active @ Self::Active { .. } => active,
430+
}
431+
}
432+
433+
fn with_extra_features(self, supported_features: SupportedFeatures) -> Self {
390434
match self {
391435
Self::Inactive => Self::Inactive,
392-
Self::NegotiatingCapabilities { surrounding_text, content_type } => {
393-
Self::Active { content_type, surrounding_text }
436+
Self::NegotiatingCapabilities(capabilities) => {
437+
Self::NegotiatingCapabilities(Capabilities { supported_features, ..capabilities })
394438
}
395439
active @ Self::Active { .. } => active,
396440
}
397441
}
442+
443+
fn with_done(self) -> Self {
444+
match self {
445+
Self::Inactive => Self::Inactive,
446+
Self::NegotiatingCapabilities(capabilities) => Self::Active(capabilities),
447+
active @ Self::Active { .. } => active,
448+
}
449+
}
398450
}
399451

400452
#[derive(Debug)]
@@ -602,7 +654,7 @@ where
602654
match event {
603655
Event::Activate => {
604656
imdata.pending_state = InputMethodEventState {
605-
active: imdata.pending_state.active.with_active(),
657+
active: imdata.pending_state.active.clone().with_active(),
606658
..Default::default()
607659
};
608660
}
@@ -611,7 +663,7 @@ where
611663
}
612664
Event::SurroundingText { text, cursor, anchor } => {
613665
imdata.pending_state = InputMethodEventState {
614-
active: imdata.pending_state.active.with_surrounding_text(),
666+
active: imdata.pending_state.active.clone().with_surrounding_text(),
615667
surrounding: SurroundingText { text, cursor, anchor },
616668
..imdata.pending_state.clone()
617669
}
@@ -633,7 +685,7 @@ where
633685
}
634686
Event::ContentType { hint, purpose } => {
635687
imdata.pending_state = InputMethodEventState {
636-
active: imdata.pending_state.active.with_content_type(),
688+
active: imdata.pending_state.active.clone().with_content_type(),
637689
content_hint: match hint {
638690
WEnum::Value(hint) => hint,
639691
WEnum::Unknown(value) => {
@@ -655,9 +707,35 @@ where
655707
..imdata.pending_state.clone()
656708
}
657709
}
710+
Event::SetAvailableActions { available_actions } => {
711+
imdata.pending_state = InputMethodEventState {
712+
active: imdata.pending_state.active.clone().with_actions(
713+
HashSet::from_iter(available_actions.iter().filter_map(|num| {
714+
Action::try_from(*num as u32)
715+
.map_err(|()| warn!("Unknown available action {num}, ignoring"))
716+
.ok()
717+
}))
718+
),
719+
..imdata.pending_state.clone()
720+
}
721+
}
722+
Event::AnnounceSupportedFeatures { features } => {
723+
imdata.pending_state = InputMethodEventState {
724+
active: imdata.pending_state.active.clone().with_extra_features(
725+
match features {
726+
WEnum::Value(v) => v,
727+
WEnum::Unknown(value) => {
728+
warn!("Unknown `features`: {value}. Assuming no extra features supported.");
729+
SupportedFeatures::empty()
730+
}
731+
}
732+
),
733+
..imdata.pending_state.clone()
734+
}
735+
}
658736
Event::Done => {
659737
imdata.pending_state = InputMethodEventState {
660-
active: imdata.pending_state.active.with_done(),
738+
active: imdata.pending_state.active.clone().with_done(),
661739
..imdata.pending_state.clone()
662740
};
663741
for (popup, state) in imdata.pending_state.popups.iter_mut() {

0 commit comments

Comments
 (0)