now_proto_pdu/session/
set_kbd_layout.rs

1use alloc::borrow::Cow;
2
3use bitflags::bitflags;
4use ironrdp_core::{
5    cast_length, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, IntoOwned, ReadCursor, WriteCursor,
6};
7
8use crate::{NowHeader, NowMessage, NowMessageClass, NowSessionMessage, NowSessionMessageKind, NowVarStr};
9
10bitflags! {
11    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12    pub struct NowSessionSetKbdLayoutFlags: u16 {
13        /// Switches to next keyboard layout. kbdLayoutId field should contain empty string.
14        /// Conflicts with NOW_SET_KBD_LAYOUT_FLAG_PREV.
15        ///
16        /// NOW_PROTO: NOW_SET_KBD_LAYOUT_FLAG_NEXT
17        const NEXT_LAYOUT = 0x0001;
18
19        /// Switches to previous keyboard layout. kbdLayoutId field should contain empty string.
20        /// Conflicts with NOW_SET_KBD_LAYOUT_FLAG_NEXT.
21        ///
22        /// NOW_PROTO: NOW_SET_KBD_LAYOUT_FLAG_PREV
23        const PREV_LAYOUT = 0x0002;
24    }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum SetKbdLayoutOption<'a> {
29    Next,
30    Prev,
31    Specific(&'a str),
32}
33
34/// The NOW_SESSION_SET_KBD_LAYOUT_MSG message is used to set the keyboard layout for the active
35/// foreground window. The request is fire-and-forget, invalid layout identifiers are ignored.
36///
37/// NOW_PROTO: NOW_SESSION_SET_KBD_LAYOUT_MSG
38#[derive(Debug, Clone, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct NowSessionSetKbdLayoutMsg<'a> {
41    flags: NowSessionSetKbdLayoutFlags,
42    layout: NowVarStr<'a>,
43}
44
45impl_pdu_borrowing!(NowSessionSetKbdLayoutMsg<'_>, OwnedNowSessionSetKbdLayoutMsg);
46
47impl IntoOwned for NowSessionSetKbdLayoutMsg<'_> {
48    type Owned = NowSessionSetKbdLayoutMsg<'static>;
49
50    fn into_owned(self) -> Self::Owned {
51        NowSessionSetKbdLayoutMsg {
52            flags: self.flags,
53            layout: self.layout.into_owned(),
54        }
55    }
56}
57
58impl<'a> NowSessionSetKbdLayoutMsg<'a> {
59    const NAME: &'static str = "NOW_SESSION_SET_KBD_LAYOUT_MSG";
60
61    pub fn new_next() -> Self {
62        Self {
63            flags: NowSessionSetKbdLayoutFlags::NEXT_LAYOUT,
64            layout: NowVarStr::default(),
65        }
66    }
67
68    pub fn new_prev() -> Self {
69        Self {
70            flags: NowSessionSetKbdLayoutFlags::PREV_LAYOUT,
71            layout: NowVarStr::default(),
72        }
73    }
74
75    pub fn new_specific(layout: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
76        let layout = NowVarStr::new(layout.into())?;
77
78        ensure_now_message_size!(layout.size());
79
80        Ok(Self {
81            flags: NowSessionSetKbdLayoutFlags::empty(),
82            layout,
83        })
84    }
85
86    pub fn layout(&'a self) -> SetKbdLayoutOption<'a> {
87        if self.flags.contains(NowSessionSetKbdLayoutFlags::NEXT_LAYOUT) {
88            return SetKbdLayoutOption::Next;
89        }
90
91        if self.flags.contains(NowSessionSetKbdLayoutFlags::PREV_LAYOUT) {
92            return SetKbdLayoutOption::Prev;
93        }
94
95        SetKbdLayoutOption::Specific(&self.layout)
96    }
97
98    fn body_size(&self) -> usize {
99        self.layout.size()
100    }
101
102    pub(super) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'a>) -> DecodeResult<Self> {
103        let flags = NowSessionSetKbdLayoutFlags::from_bits_retain(header.flags);
104        let layout = NowVarStr::decode(src)?;
105
106        if flags.contains(NowSessionSetKbdLayoutFlags::NEXT_LAYOUT)
107            && flags.contains(NowSessionSetKbdLayoutFlags::PREV_LAYOUT)
108        {
109            return Err(invalid_field_err!("flags", "both NEXT and PREV flags are set"));
110        }
111
112        Ok(Self { flags, layout })
113    }
114}
115
116impl Encode for NowSessionSetKbdLayoutMsg<'_> {
117    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
118        let header = NowHeader {
119            size: cast_length!("size", self.body_size())?,
120            class: NowMessageClass::SESSION,
121            kind: NowSessionMessageKind::SET_KBD_LAYOUT.0,
122            flags: self.flags.bits(),
123        };
124
125        header.encode(dst)?;
126        self.layout.encode(dst)?;
127
128        Ok(())
129    }
130
131    fn name(&self) -> &'static str {
132        Self::NAME
133    }
134
135    fn size(&self) -> usize {
136        NowHeader::FIXED_PART_SIZE + self.body_size()
137    }
138}
139
140impl<'de> Decode<'de> for NowSessionSetKbdLayoutMsg<'de> {
141    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
142        let header = NowHeader::decode(src)?;
143
144        match (header.class, NowSessionMessageKind(header.kind)) {
145            (NowMessageClass::SESSION, NowSessionMessageKind::SET_KBD_LAYOUT) => Self::decode_from_body(header, src),
146            _ => Err(unsupported_message_err!(class: header.class.0, kind: header.kind)),
147        }
148    }
149}
150
151impl<'a> From<NowSessionSetKbdLayoutMsg<'a>> for NowMessage<'a> {
152    fn from(msg: NowSessionSetKbdLayoutMsg<'a>) -> Self {
153        NowMessage::Session(NowSessionMessage::SetKbdLayout(msg))
154    }
155}