1use crate::connection::RequestConnection;
4use crate::cookie::{Cookie, VoidCookie};
5use crate::errors::{ConnectionError, ParseError, ReplyError};
6use crate::protocol::xproto::{self, Atom, AtomEnum, GetPropertyReply, Window};
7use crate::x11_utils::{Serialize, TryParse};
8
9macro_rules! property_cookie {
10 {
11 $(#[$meta:meta])*
12 pub struct $cookie_name:ident: $struct_name:ident,
13 $from_reply:expr,
14 } => {
15 $(#[$meta])*
16 #[derive(Debug)]
17 pub struct $cookie_name<'a, Conn: RequestConnection + ?Sized>(Cookie<'a, Conn, GetPropertyReply>);
18
19 impl<'a, Conn> $cookie_name<'a, Conn>
20 where
21 Conn: RequestConnection + ?Sized,
22 {
23 pub fn reply(self) -> Result<Option<$struct_name>, ReplyError> {
25 #[allow(clippy::redundant_closure_call)]
26 Ok($from_reply(self.0.reply()?)?)
27 }
28
29 pub fn reply_unchecked(self) -> Result<Option<$struct_name>, ConnectionError> {
31 self.0
32 .reply_unchecked()?
33 .map($from_reply)
34 .transpose()
35 .map(|e| e.flatten())
36 .map_err(Into::into)
37 }
38 }
39 }
40}
41
42property_cookie! {
45 pub struct WmClassCookie: WmClass,
49 WmClass::from_reply,
50}
51
52impl<'a, Conn> WmClassCookie<'a, Conn>
53where
54 Conn: RequestConnection + ?Sized,
55{
56 pub fn new(conn: &'a Conn, window: Window) -> Result<Self, ConnectionError> {
58 Ok(Self(xproto::get_property(
59 conn,
60 false,
61 window,
62 AtomEnum::WM_CLASS,
63 AtomEnum::STRING,
64 0,
65 2048,
66 )?))
67 }
68}
69
70#[derive(Debug)]
99pub struct WmClass(GetPropertyReply, usize);
100
101impl WmClass {
102 pub fn get<C: RequestConnection>(
104 conn: &C,
105 window: Window,
106 ) -> Result<WmClassCookie<'_, C>, ConnectionError> {
107 WmClassCookie::new(conn, window)
108 }
109
110 pub fn from_reply(reply: GetPropertyReply) -> Result<Option<Self>, ParseError> {
115 if reply.type_ == AtomEnum::NONE.into() {
116 return Ok(None);
117 }
118 if reply.type_ != AtomEnum::STRING.into() || reply.format != 8 {
119 return Err(ParseError::InvalidValue);
120 }
121 let offset = reply
123 .value
124 .iter()
125 .position(|&v| v == 0)
126 .unwrap_or(reply.value.len());
127 Ok(Some(WmClass(reply, offset)))
128 }
129
130 pub fn instance(&self) -> &[u8] {
132 &self.0.value[0..self.1]
133 }
134
135 pub fn class(&self) -> &[u8] {
137 let start = self.1 + 1;
138 if start >= self.0.value.len() {
139 return &[];
140 };
141 let end = if self.0.value.last() == Some(&0) {
142 self.0.value.len() - 1
143 } else {
144 self.0.value.len()
145 };
146 &self.0.value[start..end]
147 }
148}
149
150#[derive(Debug, Copy, Clone)]
154pub enum WmSizeHintsSpecification {
155 UserSpecified,
157 ProgramSpecified,
159}
160
161property_cookie! {
162 pub struct WmSizeHintsCookie: WmSizeHints,
164 |reply| WmSizeHints::from_reply(&reply),
165}
166
167const NUM_WM_SIZE_HINTS_ELEMENTS: u16 = 18;
168
169impl<'a, Conn> WmSizeHintsCookie<'a, Conn>
170where
171 Conn: RequestConnection + ?Sized,
172{
173 pub fn new(
175 conn: &'a Conn,
176 window: Window,
177 property: impl Into<Atom>,
178 ) -> Result<Self, ConnectionError> {
179 Ok(Self(xproto::get_property(
180 conn,
181 false,
182 window,
183 property,
184 AtomEnum::WM_SIZE_HINTS,
185 0,
186 NUM_WM_SIZE_HINTS_ELEMENTS.into(),
187 )?))
188 }
189}
190
191const U_S_POSITION: u32 = 1;
193const U_S_SIZE: u32 = 1 << 1;
194const P_S_POSITION: u32 = 1 << 2;
195const P_S_SIZE: u32 = 1 << 3;
196const P_MIN_SIZE: u32 = 1 << 4;
197const P_MAX_SIZE: u32 = 1 << 5;
198const P_RESIZE_INCREMENT: u32 = 1 << 6;
199const P_ASPECT: u32 = 1 << 7;
200const P_BASE_SIZE: u32 = 1 << 8;
201const P_WIN_GRAVITY: u32 = 1 << 9;
202
203#[derive(Debug, Copy, Clone)]
205pub struct AspectRatio {
206 pub numerator: i32,
208 pub denominator: i32,
210}
211
212impl AspectRatio {
213 pub fn new(numerator: i32, denominator: i32) -> Self {
215 Self {
216 numerator,
217 denominator,
218 }
219 }
220}
221
222impl TryParse for AspectRatio {
223 fn try_parse(value: &[u8]) -> Result<(Self, &[u8]), ParseError> {
224 let ((numerator, denominator), remaining) = TryParse::try_parse(value)?;
225 let result = AspectRatio::new(numerator, denominator);
226 Ok((result, remaining))
227 }
228}
229
230#[allow(clippy::many_single_char_names)]
231impl Serialize for AspectRatio {
232 type Bytes = [u8; 8];
233 fn serialize(&self) -> Self::Bytes {
234 let [a, b, c, d] = self.numerator.serialize();
235 let [e, f, g, h] = self.denominator.serialize();
236 [a, b, c, d, e, f, g, h]
237 }
238 fn serialize_into(&self, bytes: &mut Vec<u8>) {
239 (self.numerator, self.denominator).serialize_into(bytes);
240 }
241}
242
243#[derive(Debug, Default, Copy, Clone)]
245pub struct WmSizeHints {
246 pub position: Option<(WmSizeHintsSpecification, i32, i32)>,
251 pub size: Option<(WmSizeHintsSpecification, i32, i32)>,
256 pub min_size: Option<(i32, i32)>,
258 pub max_size: Option<(i32, i32)>,
260 pub size_increment: Option<(i32, i32)>,
262 pub aspect: Option<(AspectRatio, AspectRatio)>,
264 pub base_size: Option<(i32, i32)>,
268 pub win_gravity: Option<xproto::Gravity>,
270}
271
272impl WmSizeHints {
273 pub fn new() -> Self {
275 Default::default()
276 }
277
278 pub fn get<C: RequestConnection>(
280 conn: &C,
281 window: Window,
282 property: impl Into<Atom>,
283 ) -> Result<WmSizeHintsCookie<'_, C>, ConnectionError> {
284 WmSizeHintsCookie::new(conn, window, property)
285 }
286
287 pub fn get_normal_hints<C: RequestConnection>(
289 conn: &C,
290 window: Window,
291 ) -> Result<WmSizeHintsCookie<'_, C>, ConnectionError> {
292 Self::get(conn, window, AtomEnum::WM_NORMAL_HINTS)
293 }
294
295 pub fn from_reply(reply: &GetPropertyReply) -> Result<Option<Self>, ParseError> {
300 if reply.type_ == AtomEnum::NONE.into() {
301 return Ok(None);
302 }
303 if reply.type_ != AtomEnum::WM_SIZE_HINTS.into() || reply.format != 32 {
304 return Err(ParseError::InvalidValue);
305 }
306 Ok(Some(Self::try_parse(&reply.value)?.0))
307 }
308
309 pub fn set_normal_hints<'a, C: RequestConnection + ?Sized>(
311 &self,
312 conn: &'a C,
313 window: Window,
314 ) -> Result<VoidCookie<'a, C>, ConnectionError> {
315 self.set(conn, window, AtomEnum::WM_NORMAL_HINTS)
316 }
317
318 pub fn set<'a, C: RequestConnection + ?Sized>(
320 &self,
321 conn: &'a C,
322 window: Window,
323 property: impl Into<Atom>,
324 ) -> Result<VoidCookie<'a, C>, ConnectionError> {
325 let data = self.serialize();
326 xproto::change_property(
327 conn,
328 xproto::PropMode::REPLACE,
329 window,
330 property.into(),
331 AtomEnum::WM_SIZE_HINTS,
332 32,
333 NUM_WM_SIZE_HINTS_ELEMENTS.into(),
334 &data,
335 )
336 }
337}
338
339impl TryParse for WmSizeHints {
340 fn try_parse(remaining: &[u8]) -> Result<(Self, &[u8]), ParseError> {
341 let (flags, remaining) = u32::try_parse(remaining)?;
344 let (x, remaining) = i32::try_parse(remaining)?;
345 let (y, remaining) = i32::try_parse(remaining)?;
346 let (width, remaining) = i32::try_parse(remaining)?;
347 let (height, remaining) = i32::try_parse(remaining)?;
348 let (min_size, remaining) = parse_with_flag::<(i32, i32)>(remaining, flags, P_MIN_SIZE)?;
349 let (max_size, remaining) = parse_with_flag::<(i32, i32)>(remaining, flags, P_MAX_SIZE)?;
350 let (size_increment, remaining) =
351 parse_with_flag::<(i32, i32)>(remaining, flags, P_RESIZE_INCREMENT)?;
352 let (aspect, remaining) =
353 parse_with_flag::<(AspectRatio, AspectRatio)>(remaining, flags, P_ASPECT)?;
354 let (base_size, win_gravity, remaining) = if remaining.is_empty() {
356 (min_size, Some(xproto::Gravity::NORTH_WEST), remaining)
357 } else {
358 let (base_size, remaining) =
359 parse_with_flag::<(i32, i32)>(remaining, flags, P_BASE_SIZE)?;
360 let (win_gravity, remaining) = parse_with_flag::<u32>(remaining, flags, P_WIN_GRAVITY)?;
361 (base_size, win_gravity.map(Into::into), remaining)
362 };
363
364 let position = if flags & U_S_POSITION != 0 {
365 Some((WmSizeHintsSpecification::UserSpecified, x, y))
366 } else if flags & P_S_POSITION != 0 {
367 Some((WmSizeHintsSpecification::ProgramSpecified, x, y))
368 } else {
369 None
370 };
371 let size = if flags & U_S_SIZE != 0 {
372 Some((WmSizeHintsSpecification::UserSpecified, width, height))
373 } else if flags & P_S_SIZE != 0 {
374 Some((WmSizeHintsSpecification::ProgramSpecified, width, height))
375 } else {
376 None
377 };
378
379 Ok((
380 WmSizeHints {
381 position,
382 size,
383 min_size,
384 max_size,
385 size_increment,
386 aspect,
387 base_size,
388 win_gravity,
389 },
390 remaining,
391 ))
392 }
393}
394
395impl Serialize for WmSizeHints {
396 type Bytes = Vec<u8>;
397 fn serialize(&self) -> Self::Bytes {
398 let mut result = Vec::with_capacity((NUM_WM_SIZE_HINTS_ELEMENTS * 4).into());
399 self.serialize_into(&mut result);
400 result
401 }
402 fn serialize_into(&self, bytes: &mut Vec<u8>) {
403 let mut flags = 0;
404 match self.position {
405 Some((WmSizeHintsSpecification::UserSpecified, _, _)) => flags |= U_S_POSITION,
406 Some((WmSizeHintsSpecification::ProgramSpecified, _, _)) => flags |= P_S_POSITION,
407 None => {}
408 }
409 match self.size {
410 Some((WmSizeHintsSpecification::UserSpecified, _, _)) => flags |= U_S_SIZE,
411 Some((WmSizeHintsSpecification::ProgramSpecified, _, _)) => flags |= P_S_SIZE,
412 None => {}
413 }
414 flags |= self.min_size.map_or(0, |_| P_MIN_SIZE);
415 flags |= self.max_size.map_or(0, |_| P_MAX_SIZE);
416 flags |= self.size_increment.map_or(0, |_| P_RESIZE_INCREMENT);
417 flags |= self.aspect.map_or(0, |_| P_ASPECT);
418 flags |= self.base_size.map_or(0, |_| P_BASE_SIZE);
419 flags |= self.win_gravity.map_or(0, |_| P_WIN_GRAVITY);
420 flags.serialize_into(bytes);
421
422 match self.position {
423 Some((_, x, y)) => (x, y),
424 None => (0, 0),
425 }
426 .serialize_into(bytes);
427
428 match self.size {
429 Some((_, width, height)) => (width, height),
430 None => (0, 0),
431 }
432 .serialize_into(bytes);
433
434 self.min_size.unwrap_or((0, 0)).serialize_into(bytes);
435 self.max_size.unwrap_or((0, 0)).serialize_into(bytes);
436 self.size_increment.unwrap_or((0, 0)).serialize_into(bytes);
437 self.aspect
438 .unwrap_or((AspectRatio::new(0, 0), AspectRatio::new(0, 0)))
439 .serialize_into(bytes);
440 self.base_size.unwrap_or((0, 0)).serialize_into(bytes);
441 self.win_gravity.map_or(0, u32::from).serialize_into(bytes);
442 }
443}
444
445property_cookie! {
448 pub struct WmHintsCookie: WmHints,
452 |reply| WmHints::from_reply(&reply),
453}
454
455const NUM_WM_HINTS_ELEMENTS: u32 = 9;
456
457impl<'a, Conn> WmHintsCookie<'a, Conn>
458where
459 Conn: RequestConnection + ?Sized,
460{
461 pub fn new(conn: &'a Conn, window: Window) -> Result<Self, ConnectionError> {
463 Ok(Self(xproto::get_property(
464 conn,
465 false,
466 window,
467 AtomEnum::WM_HINTS,
468 AtomEnum::WM_HINTS,
469 0,
470 NUM_WM_HINTS_ELEMENTS,
471 )?))
472 }
473}
474
475#[derive(Debug, Copy, Clone)]
477pub enum WmHintsState {
478 Normal,
480 Iconic,
482}
483
484const HINT_INPUT: u32 = 1;
486const HINT_STATE: u32 = 1 << 1;
487const HINT_ICON_PIXMAP: u32 = 1 << 2;
488const HINT_ICON_WINDOW: u32 = 1 << 3;
489const HINT_ICON_POSITION: u32 = 1 << 4;
490const HINT_ICON_MASK: u32 = 1 << 5;
491const HINT_WINDOW_GROUP: u32 = 1 << 6;
492const HINT_URGENCY: u32 = 1 << 8;
495
496#[derive(Debug, Default, Copy, Clone)]
498pub struct WmHints {
499 pub input: Option<bool>,
503
504 pub initial_state: Option<WmHintsState>,
506
507 pub icon_pixmap: Option<xproto::Pixmap>,
509
510 pub icon_window: Option<Window>,
512
513 pub icon_position: Option<(i32, i32)>,
515
516 pub icon_mask: Option<xproto::Pixmap>,
520
521 pub window_group: Option<Window>,
526
527 pub urgent: bool,
532}
533
534impl WmHints {
535 pub fn new() -> Self {
537 Default::default()
538 }
539
540 pub fn get<C: RequestConnection>(
542 conn: &C,
543 window: Window,
544 ) -> Result<WmHintsCookie<'_, C>, ConnectionError> {
545 WmHintsCookie::new(conn, window)
546 }
547
548 pub fn from_reply(reply: &GetPropertyReply) -> Result<Option<Self>, ParseError> {
553 if reply.type_ == AtomEnum::NONE.into() {
554 return Ok(None);
555 }
556 if reply.type_ != AtomEnum::WM_HINTS.into() || reply.format != 32 {
557 return Err(ParseError::InvalidValue);
558 }
559 Ok(Some(Self::try_parse(&reply.value)?.0))
560 }
561
562 pub fn set<'a, C: RequestConnection + ?Sized>(
564 &self,
565 conn: &'a C,
566 window: Window,
567 ) -> Result<VoidCookie<'a, C>, ConnectionError> {
568 let data = self.serialize();
569 xproto::change_property(
570 conn,
571 xproto::PropMode::REPLACE,
572 window,
573 AtomEnum::WM_HINTS,
574 AtomEnum::WM_HINTS,
575 32,
576 NUM_WM_HINTS_ELEMENTS,
577 &data,
578 )
579 }
580}
581
582impl TryParse for WmHints {
583 fn try_parse(remaining: &[u8]) -> Result<(Self, &[u8]), ParseError> {
584 let (flags, remaining) = u32::try_parse(remaining)?;
585 let (input, remaining) = parse_with_flag::<u32>(remaining, flags, HINT_INPUT)?;
586 let (initial_state, remaining) = parse_with_flag::<u32>(remaining, flags, HINT_STATE)?;
587 let (icon_pixmap, remaining) = parse_with_flag::<u32>(remaining, flags, HINT_ICON_PIXMAP)?;
588 let (icon_window, remaining) = parse_with_flag::<u32>(remaining, flags, HINT_ICON_WINDOW)?;
589 let (icon_position, remaining) =
590 parse_with_flag::<(i32, i32)>(remaining, flags, HINT_ICON_POSITION)?;
591 let (icon_mask, remaining) = parse_with_flag::<u32>(remaining, flags, HINT_ICON_MASK)?;
592 let (window_group, remaining) = if remaining.is_empty() {
594 (None, remaining)
595 } else {
596 let (window_group, remaining) =
597 parse_with_flag::<u32>(remaining, flags, HINT_WINDOW_GROUP)?;
598 (window_group, remaining)
599 };
600
601 let input = input.map(|input| input != 0);
602
603 let initial_state = match initial_state {
604 None => None,
605 Some(1) => Some(WmHintsState::Normal),
606 Some(3) => Some(WmHintsState::Iconic),
607 _ => return Err(ParseError::InvalidValue),
608 };
609
610 let urgent = flags & HINT_URGENCY != 0;
611
612 Ok((
613 WmHints {
614 input,
615 initial_state,
616 icon_pixmap,
617 icon_window,
618 icon_position,
619 icon_mask,
620 window_group,
621 urgent,
622 },
623 remaining,
624 ))
625 }
626}
627
628impl Serialize for WmHints {
629 type Bytes = Vec<u8>;
630 fn serialize(&self) -> Self::Bytes {
631 let mut result = Vec::with_capacity((NUM_WM_HINTS_ELEMENTS * 4).try_into().unwrap());
633 self.serialize_into(&mut result);
634 result
635 }
636 fn serialize_into(&self, bytes: &mut Vec<u8>) {
637 let mut flags = 0;
638 flags |= self.input.map_or(0, |_| HINT_INPUT);
639 flags |= self.initial_state.map_or(0, |_| HINT_STATE);
640 flags |= self.icon_pixmap.map_or(0, |_| HINT_ICON_PIXMAP);
641 flags |= self.icon_window.map_or(0, |_| HINT_ICON_WINDOW);
642 flags |= self.icon_position.map_or(0, |_| HINT_ICON_POSITION);
643 flags |= self.icon_mask.map_or(0, |_| HINT_ICON_MASK);
644 flags |= self.window_group.map_or(0, |_| HINT_WINDOW_GROUP);
645 if self.urgent {
646 flags |= HINT_URGENCY;
647 }
648
649 flags.serialize_into(bytes);
650 u32::from(self.input.unwrap_or(false)).serialize_into(bytes);
651 match self.initial_state {
652 Some(WmHintsState::Normal) => 1,
653 Some(WmHintsState::Iconic) => 3,
654 None => 0,
655 }
656 .serialize_into(bytes);
657 self.icon_pixmap.unwrap_or(0).serialize_into(bytes);
658 self.icon_window.unwrap_or(0).serialize_into(bytes);
659 self.icon_position.unwrap_or((0, 0)).serialize_into(bytes);
660 self.icon_mask.unwrap_or(0).serialize_into(bytes);
661 self.window_group.unwrap_or(0).serialize_into(bytes);
662 }
663}
664
665fn parse_with_flag<T: TryParse>(
668 remaining: &[u8],
669 flags: u32,
670 bit: u32,
671) -> Result<(Option<T>, &[u8]), ParseError> {
672 let (value, remaining) = T::try_parse(remaining)?;
673 if flags & bit != 0 {
674 Ok((Some(value), remaining))
675 } else {
676 Ok((None, remaining))
677 }
678}
679
680#[cfg(test)]
681mod test {
682 use super::{WmClass, WmHints, WmHintsState, WmSizeHints};
683 use crate::protocol::xproto::{Atom, AtomEnum, GetPropertyReply, Gravity};
684 use crate::x11_utils::Serialize;
685
686 fn get_property_reply(value: &[u8], format: u8, type_: impl Into<Atom>) -> GetPropertyReply {
687 GetPropertyReply {
688 format,
689 sequence: 0,
690 length: 0,
691 type_: type_.into(),
692 bytes_after: 0,
693 value_len: value.len().try_into().unwrap(),
694 value: value.to_vec(),
695 }
696 }
697
698 #[test]
699 fn test_wm_class() {
700 for (input, instance, class) in &[
701 (&b""[..], &b""[..], &b""[..]),
702 (b"\0", b"", b""),
703 (b"\0\0", b"", b""),
704 (b"\0\0\0", b"", b"\0"),
705 (b"Hello World", b"Hello World", b""),
706 (b"Hello World\0", b"Hello World", b""),
707 (b"Hello\0World\0", b"Hello", b"World"),
708 (b"Hello\0World", b"Hello", b"World"),
709 (b"Hello\0World\0Good\0Day", b"Hello", b"World\0Good\0Day"),
710 ] {
711 let wm_class = WmClass::from_reply(get_property_reply(input, 8, AtomEnum::STRING))
712 .unwrap()
713 .unwrap();
714 assert_eq!((wm_class.instance(), wm_class.class()), (*instance, *class));
715 }
716 }
717
718 #[test]
719 fn test_wm_class_missing() {
720 let wm_class = WmClass::from_reply(get_property_reply(&[], 0, AtomEnum::NONE)).unwrap();
721 assert!(wm_class.is_none());
722 }
723
724 #[test]
725 fn test_wm_normal_hints() {
726 let input = [
729 0x0000_0350,
730 0x0000_0000,
731 0x0000_0000,
732 0x0000_0000,
733 0x0000_0000,
734 0x0000_0015,
735 0x0000_0017,
736 0x0000_0000,
737 0x0000_0000,
738 0x0000_000a,
739 0x0000_0013,
740 0x0000_0000,
741 0x0000_0000,
742 0x0000_0000,
743 0x0000_0000,
744 0x0000_000b,
745 0x0000_0004,
746 0x0000_0001,
747 ];
748 let input = input
749 .iter()
750 .flat_map(|v| u32::serialize(v).to_vec())
751 .collect::<Vec<u8>>();
752 let wm_size_hints =
753 WmSizeHints::from_reply(&get_property_reply(&input, 32, AtomEnum::WM_SIZE_HINTS))
754 .unwrap()
755 .unwrap();
756
757 assert!(
758 wm_size_hints.position.is_none(),
759 "{:?}",
760 wm_size_hints.position,
761 );
762 assert!(wm_size_hints.size.is_none(), "{:?}", wm_size_hints.size);
763 assert_eq!(wm_size_hints.min_size, Some((21, 23)));
764 assert_eq!(wm_size_hints.max_size, None);
765 assert_eq!(wm_size_hints.size_increment, Some((10, 19)));
766 assert!(wm_size_hints.aspect.is_none(), "{:?}", wm_size_hints.aspect);
767 assert_eq!(wm_size_hints.base_size, Some((11, 4)));
768 assert_eq!(wm_size_hints.win_gravity, Some(Gravity::NORTH_WEST));
769
770 assert_eq!(input, wm_size_hints.serialize());
771 }
772
773 #[test]
774 fn test_wm_normal_hints_missing() {
775 let wm_size_hints =
776 WmSizeHints::from_reply(&get_property_reply(&[], 0, AtomEnum::NONE)).unwrap();
777 assert!(wm_size_hints.is_none());
778 }
779
780 #[test]
781 fn test_wm_hints() {
782 let input = [
785 0x0000_0043,
786 0x0000_0001,
787 0x0000_0001,
788 0x0000_0000,
789 0x0000_0000,
790 0x0000_0000,
791 0x0000_0000,
792 0x0000_0000,
793 0x0060_0009,
794 ];
795 let input = input
796 .iter()
797 .flat_map(|v| u32::serialize(v).to_vec())
798 .collect::<Vec<u8>>();
799 let wm_hints = WmHints::from_reply(&get_property_reply(&input, 32, AtomEnum::WM_HINTS))
800 .unwrap()
801 .unwrap();
802
803 assert_eq!(wm_hints.input, Some(true));
804 match wm_hints.initial_state {
805 Some(WmHintsState::Normal) => {}
806 value => panic!("Expected Some(Normal), but got {:?}", value),
807 }
808 assert_eq!(wm_hints.icon_pixmap, None);
809 assert_eq!(wm_hints.icon_window, None);
810 assert_eq!(wm_hints.icon_position, None);
811 assert_eq!(wm_hints.icon_mask, None);
812 assert_eq!(wm_hints.window_group, Some(0x0060_0009));
813 assert!(!wm_hints.urgent);
814
815 assert_eq!(input, wm_hints.serialize());
816 }
817
818 #[test]
819 fn test_wm_hints_missing() {
820 let wm_hints = WmHints::from_reply(&get_property_reply(&[], 0, AtomEnum::NONE)).unwrap();
821 assert!(wm_hints.is_none());
822 }
823}