1use alloc::borrow::Cow;
2
3use bitflags::bitflags;
4use ironrdp_core::{
5 cast_length, other_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)]
13 struct WindowRecEventFlags: u16 {
14 const ACTIVE_WINDOW = 0x0001;
16 const TITLE_CHANGED = 0x0002;
18 const NO_ACTIVE_WINDOW = 0x0004;
20 }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct ActiveWindowEventData<'a> {
28 process_id: u32,
29 title: NowVarStr<'a>,
30 executable_path: NowVarStr<'a>,
31}
32
33impl ActiveWindowEventData<'_> {
34 pub fn process_id(&self) -> u32 {
36 self.process_id
37 }
38
39 pub fn title(&self) -> &str {
41 self.title.as_ref()
42 }
43
44 pub fn executable_path(&self) -> &str {
46 self.executable_path.as_ref()
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TitleChangedEventData<'a> {
55 title: NowVarStr<'a>,
56}
57
58impl TitleChangedEventData<'_> {
59 pub fn title(&self) -> &str {
61 self.title.as_ref()
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum WindowRecEventKind<'a> {
70 ActiveWindow(ActiveWindowEventData<'a>),
74 TitleChanged(TitleChangedEventData<'a>),
78 NoActiveWindow,
82}
83
84pub type OwnedActiveWindowEventData = ActiveWindowEventData<'static>;
85
86impl IntoOwned for ActiveWindowEventData<'_> {
87 type Owned = OwnedActiveWindowEventData;
88
89 fn into_owned(self) -> Self::Owned {
90 OwnedActiveWindowEventData {
91 process_id: self.process_id,
92 title: self.title.into_owned(),
93 executable_path: self.executable_path.into_owned(),
94 }
95 }
96}
97
98pub type OwnedTitleChangedEventData = TitleChangedEventData<'static>;
99
100impl IntoOwned for TitleChangedEventData<'_> {
101 type Owned = OwnedTitleChangedEventData;
102
103 fn into_owned(self) -> Self::Owned {
104 OwnedTitleChangedEventData {
105 title: self.title.into_owned(),
106 }
107 }
108}
109
110pub type OwnedWindowRecEventKind = WindowRecEventKind<'static>;
111
112impl IntoOwned for WindowRecEventKind<'_> {
113 type Owned = OwnedWindowRecEventKind;
114
115 fn into_owned(self) -> Self::Owned {
116 match self {
117 Self::ActiveWindow(data) => OwnedWindowRecEventKind::ActiveWindow(data.into_owned()),
118 Self::TitleChanged(data) => OwnedWindowRecEventKind::TitleChanged(data.into_owned()),
119 Self::NoActiveWindow => OwnedWindowRecEventKind::NoActiveWindow,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct NowSessionWindowRecEventMsg<'a> {
130 timestamp: u64,
131 kind: WindowRecEventKind<'a>,
132}
133
134impl_pdu_borrowing!(NowSessionWindowRecEventMsg<'_>, OwnedNowSessionWindowRecEventMsg);
135
136impl IntoOwned for NowSessionWindowRecEventMsg<'_> {
137 type Owned = OwnedNowSessionWindowRecEventMsg;
138
139 fn into_owned(self) -> Self::Owned {
140 OwnedNowSessionWindowRecEventMsg {
141 timestamp: self.timestamp,
142 kind: self.kind.into_owned(),
143 }
144 }
145}
146
147impl<'a> NowSessionWindowRecEventMsg<'a> {
148 const NAME: &'static str = "NOW_SESSION_WINDOW_REC_EVENT_MSG";
149 const FIXED_PART_SIZE: usize = 8 + 4; pub fn active_window(
152 timestamp: u64,
153 process_id: u32,
154 title: impl Into<Cow<'a, str>>,
155 executable_path: impl Into<Cow<'a, str>>,
156 ) -> EncodeResult<Self> {
157 Ok(Self {
158 timestamp,
159 kind: WindowRecEventKind::ActiveWindow(ActiveWindowEventData {
160 process_id,
161 title: NowVarStr::new(title)?,
162 executable_path: NowVarStr::new(executable_path)?,
163 }),
164 })
165 }
166
167 pub fn title_changed(timestamp: u64, title: impl Into<Cow<'a, str>>) -> EncodeResult<Self> {
168 Ok(Self {
169 timestamp,
170 kind: WindowRecEventKind::TitleChanged(TitleChangedEventData {
171 title: NowVarStr::new(title)?,
172 }),
173 })
174 }
175
176 pub fn no_active_window(timestamp: u64) -> Self {
177 Self {
178 timestamp,
179 kind: WindowRecEventKind::NoActiveWindow,
180 }
181 }
182
183 pub fn timestamp(&self) -> u64 {
184 self.timestamp
185 }
186
187 pub fn kind(&self) -> &WindowRecEventKind<'a> {
188 &self.kind
189 }
190}
191
192impl Encode for NowSessionWindowRecEventMsg<'_> {
193 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
194 let mut title = NowVarStr::default();
195 let mut executable_path = NowVarStr::default();
196
197 let (flags, process_id) = match &self.kind {
198 WindowRecEventKind::ActiveWindow(data) => {
199 title = data.title.clone();
200 executable_path = data.executable_path.clone();
201 (WindowRecEventFlags::ACTIVE_WINDOW, data.process_id)
202 }
203 WindowRecEventKind::TitleChanged(data) => {
204 title = data.title.clone();
205 (WindowRecEventFlags::TITLE_CHANGED, 0)
206 }
207 WindowRecEventKind::NoActiveWindow => (WindowRecEventFlags::NO_ACTIVE_WINDOW, 0),
208 };
209
210 let header = NowHeader {
211 size: cast_length!("size", self.body_size())?,
212 class: NowMessageClass::SESSION,
213 kind: NowSessionMessageKind::WINDOW_REC_EVENT.0,
214 flags: flags.bits(),
215 };
216
217 header.encode(dst)?;
218 dst.write_u64(self.timestamp);
219 dst.write_u32(process_id);
220 title.encode(dst)?;
221 executable_path.encode(dst)?;
222
223 Ok(())
224 }
225
226 fn name(&self) -> &'static str {
227 Self::NAME
228 }
229
230 fn size(&self) -> usize {
231 NowHeader::FIXED_PART_SIZE + self.body_size()
232 }
233}
234
235impl NowSessionWindowRecEventMsg<'_> {
236 fn body_size(&self) -> usize {
237 let empty_str_size = NowVarStr::new("").expect("empty string is valid").size();
239
240 let (title_size, exec_path_size) = match &self.kind {
241 WindowRecEventKind::ActiveWindow(data) => (data.title.size(), data.executable_path.size()),
242 WindowRecEventKind::TitleChanged(data) => (data.title.size(), empty_str_size),
243 WindowRecEventKind::NoActiveWindow => (empty_str_size, empty_str_size),
244 };
245
246 Self::FIXED_PART_SIZE + title_size + exec_path_size
247 }
248}
249
250impl<'de> Decode<'de> for NowSessionWindowRecEventMsg<'de> {
251 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
252 let header = NowHeader::decode(src)?;
253
254 match (header.class, NowSessionMessageKind(header.kind)) {
255 (NowMessageClass::SESSION, NowSessionMessageKind::WINDOW_REC_EVENT) => Self::decode_from_body(header, src),
256 _ => Err(unsupported_message_err!(class: header.class.0, kind: header.kind)),
257 }
258 }
259}
260
261impl<'de> NowSessionWindowRecEventMsg<'de> {
262 pub(crate) fn decode_from_body(header: NowHeader, src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
263 let flags = WindowRecEventFlags::from_bits_retain(header.flags);
264 let timestamp = src.read_u64();
265 let process_id = src.read_u32();
266 let title = NowVarStr::decode(src)?;
267 let executable_path = NowVarStr::decode(src)?;
268
269 let kind = if flags.contains(WindowRecEventFlags::ACTIVE_WINDOW) {
270 WindowRecEventKind::ActiveWindow(ActiveWindowEventData {
271 process_id,
272 title,
273 executable_path,
274 })
275 } else if flags.contains(WindowRecEventFlags::TITLE_CHANGED) {
276 WindowRecEventKind::TitleChanged(TitleChangedEventData { title })
277 } else if flags.contains(WindowRecEventFlags::NO_ACTIVE_WINDOW) {
278 WindowRecEventKind::NoActiveWindow
279 } else {
280 return Err(other_err!(
281 "invalid window recording event flags",
282 "unsupported flags combination"
283 ));
284 };
285
286 Ok(Self { timestamp, kind })
287 }
288}
289
290impl<'a> From<NowSessionWindowRecEventMsg<'a>> for NowMessage<'a> {
291 fn from(value: NowSessionWindowRecEventMsg<'a>) -> Self {
292 Self::Session(NowSessionMessage::WindowRecEvent(value))
293 }
294}