use xcb::{ x::{self, Atom, ChangeProperty, GetProperty, Gravity, Pixmap, PropMode, Window}, Xid, XidNew, }; use crate::conn_info::Connection; /// EWM Hints set on a window. pub struct Ewm { flags: u32, input: bool, initial_state: i32, icon_pixmap: Pixmap, icon_window: Window, icon_x: i32, icon_y: i32, icon_mask: Pixmap, window_group: u32, } impl Ewm { /// Get the EWM hints for the given window, if they exist and are valid. pub fn get(conn: &Connection<'_>, window: Window) -> Option { // https://github.com/mirror/libX11/blob/ff8706a5eae25b8bafce300527079f68a201d27f/src/GetHints.c#L106 // https://github.com/mirror/libX11/blob/master/src/Xatomtype.h#L111 let hints = conn .wait_for_reply(conn.send_request(&GetProperty { window, delete: false, property: x::ATOM_WM_HINTS, r#type: x::ATOM_WM_HINTS, long_offset: 0, long_length: 9, })) .ok()?; if hints.r#type() != x::ATOM_WM_HINTS || hints.length() < 8 || hints.format() != 32 { return None; } let [flags, input, initial_state, icon_pixmap, icon_window, icon_x, icon_y, icon_mask, window_group] = match hints.value::() { [f, i, is, ip, iw, ix, iy, im, wg] => [f, i, is, ip, iw, ix, iy, im, wg], [f, i, is, ip, iw, ix, iy, im] => [f, i, is, ip, iw, ix, iy, im, &0], _ => unreachable!(), }; unsafe { Some(Self { flags: *flags, input: *input > 0, initial_state: std::mem::transmute::(*initial_state), icon_pixmap: Pixmap::new(*icon_pixmap), icon_window: Window::new(*icon_window), icon_x: std::mem::transmute::(*icon_x), icon_y: std::mem::transmute::(*icon_y), icon_mask: Pixmap::new(*icon_mask), window_group: *window_group, }) } } /// Set these WM hints on the given window. pub fn apply(&self, conn: &Connection<'_>, window: Window) { conn.send_request(&ChangeProperty { mode: PropMode::Replace, window, property: x::ATOM_WM_HINTS, r#type: x::ATOM_WM_HINTS, data: unsafe { &[ self.flags, u32::from(self.input), std::mem::transmute::(self.initial_state), self.icon_pixmap.resource_id(), self.icon_window.resource_id(), std::mem::transmute::(self.icon_x), std::mem::transmute::(self.icon_y), self.icon_mask.resource_id(), self.window_group, ] }, }); } /// If the window is flagged as urgent pub const fn is_urgent(&self) -> bool { (self.flags & 1 << 8) > 0 } /// Set the urgent flag. [`Self::apply`] should be called afterwards. pub fn set_urgent(&mut self, urgent: bool) { self.flags &= u32::from(urgent) << 8; } /// Whether the window has the `input` flag set, unset, or not specified. pub const fn input(&self) -> Option { if (self.flags & 1 << 0) > 0 { Some(self.input) } else { None } } } #[derive(Debug)] pub struct Size { /// The minimum size this window needs to be useful (width, height). min_size: Option<(u32, u32)>, /// The maximum size at which this window can be useful (width, height). max_size: Option<(u32, u32)>, /// The increments in which to resize this window (width, height) resize_increments: Option<(u32, u32)>, /// The ideal base size of this window (width, height) base_size: Option<(u32, u32)>, /// What side of the screen to gravitate this window towards. win_gravity: Gravity, } impl Size { const PROGRAM_MIN_SIZE: u32 = (1 << 4); const PROGRAM_MAX_SIZE: u32 = (1 << 5); const PROGRAM_RESIZE_INC: u32 = (1 << 6); const PROGRAM_BASE_SIZE: u32 = (1 << 8); const PROGRAM_WIN_GRAVITY: u32 = (1 << 9); /// Get the size hints for the given window, if they exist and are valid. pub fn get(conn: &Connection<'_>, window: Window) -> Option { // https://github.com/mirror/libX11/blob/ff8706a5eae25b8bafce300527079f68a201d27f/src/GetHints.c#L59 // https://github.com/mirror/libX11/blob/master/src/Xatomtype.h#L77 let hints = conn .wait_for_reply(conn.send_request(&GetProperty { window, delete: false, property: x::ATOM_WM_NORMAL_HINTS, r#type: x::ATOM_WM_SIZE_HINTS, long_offset: 0, long_length: 18, })) .ok()?; if hints.r#type() != x::ATOM_WM_SIZE_HINTS || hints.length() < 18 || hints.format() != 32 { return None; } let &[flags, _, _, _, _, min_width, min_height, max_width, max_height, width_inc, height_inc, _, _, _, _, base_width, base_height, win_gravity] = hints.value::() else { unreachable!() }; Some(Self { min_size: if (flags & Self::PROGRAM_MIN_SIZE) > 0 { Some((min_width, min_height)) } else if (flags & Self::PROGRAM_BASE_SIZE) > 0 { Some((base_width, base_height)) } else { None }, max_size: if (flags & Self::PROGRAM_MAX_SIZE) > 0 { Some((max_width, max_height)) } else { None }, resize_increments: if (flags & Self::PROGRAM_RESIZE_INC) > 0 { Some((width_inc, height_inc)) } else { None }, base_size: if (flags & Self::PROGRAM_BASE_SIZE) > 0 { Some((base_width, base_height)) } else if (flags & Self::PROGRAM_MIN_SIZE) > 0 { Some((min_width, min_height)) } else { None }, win_gravity: if (flags & Self::PROGRAM_WIN_GRAVITY) > 0 { match win_gravity { 2 => Gravity::North, 3 => Gravity::NorthEast, 4 => Gravity::West, 5 => Gravity::Center, 6 => Gravity::East, 7 => Gravity::SouthWest, 8 => Gravity::South, 9 => Gravity::SouthEast, _ => Gravity::NorthWest, } } else { Gravity::NorthWest }, }) } } /// Gets the `WM_TRANSIENT_FOR` hint set on a window pub fn transient_for(conn: &Connection, window: Window) -> Option { let hints = conn .wait_for_reply(conn.send_request(&GetProperty { window, delete: false, property: x::ATOM_WM_TRANSIENT_FOR, r#type: x::ATOM_WINDOW, long_offset: 0, long_length: 1, })) .ok()?; if hints.r#type() != x::ATOM_WINDOW || hints.length() < 1 || hints.format() != 32 { return None; } Some(hints.value::()[0]) } /// Check if the given window is flagged as a dialog with the `NET_WM_WINDOW_TYPE` property pub fn is_dialog(conn: &Connection, window: Window) -> bool { let Ok(hints) = conn.wait_for_reply(conn.send_request(&GetProperty { window, delete: false, property: conn.atoms.net_wm_window_type, r#type: x::ATOM_ATOM, long_offset: 0, long_length: 1, })) else { return false; }; hints.r#type() == x::ATOM_ATOM && hints.length() == 1 && hints.format() == 32 && hints.value::()[0] == conn.atoms.net_wm_window_type_dialog } /// Check if the given window wants to be fullscreen, using the `NET_WM_STATE` property pub fn is_fullscreen(conn: &Connection, window: Window) -> bool { let Ok(hints) = conn.wait_for_reply(conn.send_request(&GetProperty { window, delete: false, property: conn.atoms.net_wm_state, r#type: x::ATOM_ATOM, long_offset: 0, long_length: 9999, })) else { return false; }; hints.r#type() == x::ATOM_ATOM && hints.format() == 32 && hints .value::() .iter() .any(|a| *a == conn.atoms.net_wm_fullscreen) }