use crate::{
core::{
bindings::{KeyCode, KeyCodeMask, KeyCodeValue, MouseEvent, MouseState},
data_types::{Point, Region, WinType},
helpers::spawn_for_output,
screen::Screen,
xconnection::{
Atom, ClientAttr, ClientConfig, ClientEventMask, ClientMessage, ClientMessageData,
ClientMessageKind, ConfigureEvent, ExposeEvent, MapState, PointerChange, Prop,
PropertyEvent, WindowAttributes, WindowClass, WindowState, WmHints, WmNormalHints,
XAtomQuerier, XEvent, Xid,
},
},
xcb::{Result, XErrorCode, XcbError, XcbGenericEvent},
};
use strum::*;
use std::{collections::HashMap, convert::TryFrom, fmt, str::FromStr};
#[cfg(feature = "keysyms")]
use crate::core::{bindings::KeyPress, xconnection::KeyPressParseAttempt};
#[cfg(feature = "keysyms")]
use penrose_keysyms::XKeySym;
pub type ReverseCodeMap = HashMap<(KeyCodeMask, KeyCodeValue), String>;
const RANDR_MAJ: u32 = 1;
const RANDR_MIN: u32 = 2;
#[cfg(feature = "serde")]
fn default_conn() -> xcb::Connection {
let (conn, _) = xcb::Connection::connect(None).expect("unable to connect using XCB");
conn
}
pub fn code_map_from_xmodmap() -> Result<ReverseCodeMap> {
let output = match spawn_for_output("xmodmap -pke") {
Ok(s) => s,
Err(e) => return Err(XcbError::Raw(e.to_string())), };
Ok(output
.lines()
.flat_map(|l| {
let mut words = l.split_whitespace(); let key_code: u8 = match words.nth(1) {
Some(word) => match word.parse() {
Ok(val) => val,
Err(e) => panic!("{}", e),
},
None => panic!("unexpected output format from xmodmap -pke"),
};
vec![
words.nth(1).map(move |name| ((0, key_code), name.into())),
words.next().map(move |name| ((1, key_code), name.into())),
]
.into_iter()
.flatten()
})
.collect::<HashMap<(u16, u8), String>>())
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Api {
#[cfg_attr(feature = "serde", serde(skip, default = "default_conn"))]
conn: xcb::Connection,
root: Xid,
randr_base: u8,
atoms: HashMap<Atom, u32>,
#[cfg(feature = "keysyms")]
code_map: ReverseCodeMap,
}
impl fmt::Debug for Api {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("XCB Api")
.field("root", &self.root)
.field("randr_base", &self.randr_base)
.field("atoms", &self.atoms)
.finish()
}
}
impl XAtomQuerier for Api {
fn atom_name(&self, atom: Xid) -> crate::core::xconnection::Result<String> {
Ok(self.atom_name(atom)?)
}
fn atom_id(&self, name: &str) -> crate::core::xconnection::Result<Xid> {
Ok(self.atom(name)?)
}
}
impl Api {
pub fn new() -> Result<Self> {
let (conn, _) = xcb::Connection::connect(None)?;
let mut api = Self {
conn,
root: 0,
randr_base: 0,
atoms: HashMap::new(),
#[cfg(feature = "keysyms")]
code_map: code_map_from_xmodmap()?,
};
api.init()?;
Ok(api)
}
fn init(&mut self) -> Result<()> {
self.root = match self.conn.get_setup().roots().next() {
Some(r) => r.root(),
None => return Err(XcbError::NoScreens),
};
self.randr_base = self
.conn
.get_extension_data(&mut xcb::randr::id())
.ok_or_else(|| XcbError::Randr("unable to fetch extension data".into()))?
.first_event();
let cookie = xcb::randr::query_version(&self.conn, RANDR_MAJ, RANDR_MIN);
let reply = cookie.get_reply()?;
let (maj, min) = (reply.major_version(), reply.minor_version());
if (maj, min) != (RANDR_MAJ, RANDR_MIN) {
return Err(XcbError::Randr(format!(
"penrose requires RandR version >= {}.{}: detected {}.{}\nplease update RandR to a newer version",
RANDR_MAJ, RANDR_MIN, maj, min
)));
}
self.atoms = Atom::iter()
.map(|atom| {
let val = self.atom(atom.as_ref())?;
Ok((atom, val))
})
.collect::<Result<HashMap<_, _>>>()?;
Ok(())
}
pub(crate) fn known_atoms(&self) -> &HashMap<Atom, u32> {
&self.atoms
}
pub fn atom_name(&self, atom: Xid) -> Result<String> {
Ok(xcb::get_atom_name(&self.conn, atom)
.get_reply()?
.name()
.to_string())
}
pub fn list_props(&self, id: Xid) -> Result<Vec<String>> {
xcb::list_properties(&self.conn, id)
.get_reply()?
.atoms()
.iter()
.map(|a| self.atom_name(*a))
.collect::<Result<Vec<String>>>()
}
pub fn conn(&self) -> &xcb::Connection {
&self.conn
}
pub(crate) fn screen(&self, ix: usize) -> Result<xcb::Screen<'_>> {
let mut roots: Vec<_> = self.conn.get_setup().roots().collect();
let len = roots.len();
if ix >= len {
Err(XcbError::UnknownScreen(ix, len - 1))
} else {
Ok(roots.remove(ix))
}
}
pub(crate) fn get_depth<'a>(&self, screen: &'a xcb::Screen<'_>) -> Result<xcb::Depth<'a>> {
screen
.allowed_depths()
.max_by(|x, y| x.depth().cmp(&y.depth()))
.ok_or(XcbError::QueryFailed("screen depth"))
}
pub(crate) fn get_visual_type<'a>(&self, depth: &xcb::Depth<'a>) -> Result<xcb::Visualtype> {
depth
.visuals()
.find(|v| v.class() == xcb::VISUAL_CLASS_TRUE_COLOR as u8)
.ok_or(XcbError::QueryFailed("visual type"))
}
pub fn get_prop(&self, id: Xid, name: &str) -> Result<Prop> {
let atom = self.atom(name)?;
let cookie = xcb::get_property(&self.conn, false, id, atom, xcb::ATOM_ANY, 0, 1024);
let r = cookie.get_reply()?;
let prop_type = self.atom_name(r.type_())?;
Ok(match prop_type.as_ref() {
"ATOM" => Prop::Atom(
r.value()
.iter()
.map(|a| self.atom_name(*a))
.collect::<Result<Vec<String>>>()?,
),
"CARDINAL" => Prop::Cardinal(r.value()[0]),
"STRING" => Prop::UTF8String(
String::from_utf8_lossy(r.value())
.trim_matches('\0')
.split('\0')
.map(|s| s.to_string())
.collect(),
),
"UTF8_STRING" => Prop::UTF8String(
String::from_utf8(r.value().to_vec())?
.trim_matches('\0')
.split('\0')
.map(|s| s.to_string())
.collect(),
),
"WINDOW" => Prop::Window(r.value().to_vec()),
"WM_HINTS" => Prop::WmHints(
WmHints::try_from_bytes(r.value())
.map_err(|e| XcbError::InvalidPropertyData(e.to_string()))?,
),
"WM_SIZE_HINTS" => Prop::WmNormalHints(
WmNormalHints::try_from_bytes(r.value())
.map_err(|e| XcbError::InvalidPropertyData(e.to_string()))?,
),
_ => Prop::Bytes(match r.format() {
8 => r.value::<u8>().iter().map(|b| *b as u32).collect(),
16 => r.value::<u16>().iter().map(|b| *b as u32).collect(),
32 => r.value::<u32>().to_vec(),
_ => {
return Err(XcbError::InvalidPropertyData(format!(
"prop type for {} was {} which claims to have a data format of {}",
name,
prop_type,
r.type_()
)))
}
}),
})
}
pub fn get_window_attributes(&self, id: Xid) -> Result<WindowAttributes> {
let win_attrs = xcb::get_window_attributes(&self.conn, id).get_reply()?;
let override_redirect = win_attrs.override_redirect();
let map_state = match win_attrs.map_state() {
0 => MapState::Unmapped,
1 => MapState::UnViewable,
2 => MapState::Viewable,
s => return Err(XcbError::Raw(format!("invalid map state: {}", s))),
};
let window_class = match win_attrs.class() {
0 => WindowClass::CopyFromParent,
1 => WindowClass::InputOutput,
2 => WindowClass::InputOnly,
c => return Err(XcbError::Raw(format!("invalid window class: {}", c))),
};
Ok(WindowAttributes::new(
override_redirect,
map_state,
window_class,
))
}
pub fn grab_keyboard(&self) -> Result<()> {
xcb::grab_keyboard(
&self.conn,
true,
self.root(),
xcb::CURRENT_TIME,
xcb::GRAB_MODE_ASYNC as u8,
xcb::GRAB_MODE_ASYNC as u8,
)
.get_reply()?;
Ok(())
}
pub fn ungrab_keyboard(&self) -> Result<()> {
xcb::ungrab_keyboard_checked(&self.conn, xcb::CURRENT_TIME).request_check()?;
Ok(())
}
#[cfg(feature = "keysyms")]
pub fn next_keypress(&self) -> Result<Option<KeyPressParseAttempt>> {
if let Some(event) = self.conn.poll_for_event() {
let attempt = self.attempt_to_parse_as_keypress(event);
if let Ok(Some(_)) = attempt {
return attempt;
}
}
Ok(self.conn.has_error().map(|_| None)?)
}
#[cfg(feature = "keysyms")]
pub fn next_keypress_blocking(&self) -> Result<KeyPressParseAttempt> {
loop {
if let Some(event) = self.conn.wait_for_event() {
let attempt = self.attempt_to_parse_as_keypress(event);
if let Ok(Some(k)) = attempt {
return Ok(k);
}
}
if let Err(e) = self.conn.has_error() {
return Err(e.into());
}
}
}
#[cfg(feature = "keysyms")]
fn attempt_to_parse_as_keypress(
&self,
event: XcbGenericEvent,
) -> Result<Option<KeyPressParseAttempt>> {
if let Ok(k) = KeyCode::try_from(&event) {
if let Some(s) = self.code_map.get(&(k.mask, k.code)) {
if let Ok(k) = KeyPress::try_from(XKeySym::from_str(s)?) {
return Ok(Some(KeyPressParseAttempt::KeyPress(k)));
}
}
}
if let Some(e) = self.generic_xcb_to_xevent(event)? {
return Ok(Some(KeyPressParseAttempt::XEvent(e)));
}
Ok(None)
}
fn generic_xcb_to_xevent(&self, event: XcbGenericEvent) -> Result<Option<XEvent>> {
let xcb_response_type_mask: u8 = 0x7F;
let numlock = xcb::MOD_MASK_2 as u16;
let etype = event.response_type() & xcb_response_type_mask;
if etype == self.randr_base + xcb::randr::NOTIFY {
return Ok(Some(XEvent::RandrNotify));
} else if etype == self.randr_base + xcb::randr::SCREEN_CHANGE_NOTIFY {
return Ok(Some(XEvent::ScreenChange));
}
Ok(match etype {
xcb::BUTTON_PRESS | xcb::BUTTON_RELEASE | xcb::MOTION_NOTIFY => {
match MouseEvent::try_from(event) {
Ok(m) => Some(XEvent::MouseEvent(m)),
Err(_) => {
warn!("dropping unknown mouse button event");
None }
}
}
xcb::KEY_PRESS => Some(XEvent::KeyPress(
KeyCode::try_from(event)?.ignoring_modifier(numlock),
)),
xcb::MAP_REQUEST => {
let e: &xcb::MapRequestEvent = unsafe { xcb::cast_event(&event) };
let id = e.window();
xcb::xproto::get_window_attributes(&self.conn, id)
.get_reply()
.ok()
.map(|r| XEvent::MapRequest(id, r.override_redirect()))
}
xcb::ENTER_NOTIFY => {
let e: &xcb::EnterNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Enter(PointerChange {
id: e.event(),
abs: Point::new(e.root_x() as u32, e.root_y() as u32),
relative: Point::new(e.event_x() as u32, e.event_y() as u32),
}))
}
xcb::LEAVE_NOTIFY => {
let e: &xcb::LeaveNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Leave(PointerChange {
id: e.event(),
abs: Point::new(e.root_x() as u32, e.root_y() as u32),
relative: Point::new(e.event_x() as u32, e.event_y() as u32),
}))
}
xcb::FOCUS_IN => {
let e: &xcb::FocusInEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::FocusIn(e.event()))
}
xcb::DESTROY_NOTIFY => {
let e: &xcb::DestroyNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Destroy(e.window()))
}
xcb::CONFIGURE_NOTIFY => {
let e: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::ConfigureNotify(ConfigureEvent {
id: e.window(),
r: Region::new(
e.x() as u32,
e.y() as u32,
e.width() as u32,
e.height() as u32,
),
is_root: e.window() == self.root,
}))
}
xcb::CONFIGURE_REQUEST => {
let e: &xcb::ConfigureNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::ConfigureRequest(ConfigureEvent {
id: e.window(),
r: Region::new(
e.x() as u32,
e.y() as u32,
e.width() as u32,
e.height() as u32,
),
is_root: e.window() == self.root,
}))
}
xcb::EXPOSE => {
let e: &xcb::ExposeEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Expose(ExposeEvent {
id: e.window(),
r: Region::new(
e.x() as u32,
e.y() as u32,
e.width() as u32,
e.height() as u32,
),
count: e.count() as usize,
}))
}
xcb::UNMAP_NOTIFY => {
let e: &xcb::UnmapNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::UnmapNotify(e.window()))
}
xcb::CLIENT_MESSAGE => {
let e: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(&event) };
xcb::xproto::get_atom_name(&self.conn, e.type_())
.get_reply()
.map_err(XcbError::from)
.and_then(|a| {
Ok(ClientMessage::new(
e.window(),
ClientEventMask::NoEventMask,
a.name(),
match e.format() {
8 => ClientMessageData::try_from(e.data().data8()),
16 => ClientMessageData::try_from(e.data().data16()),
32 => ClientMessageData::try_from(e.data().data32()),
_ => unreachable!(
"ClientMessageEvent.format should really be an enum..."
),
}
.map_err(|_| XcbError::InvalidClientMessage(e.format()))?,
))
})
.map(XEvent::ClientMessage)
.ok()
}
xcb::PROPERTY_NOTIFY => {
let e: &xcb::PropertyNotifyEvent = unsafe { xcb::cast_event(&event) };
xcb::xproto::get_atom_name(&self.conn, e.atom())
.get_reply()
.ok()
.map(|a| {
XEvent::PropertyNotify(PropertyEvent {
id: e.window(),
atom: a.name().to_string(),
is_root: e.window() == self.root,
})
})
}
0 => {
let e: &xcb::GenericError = unsafe { xcb::cast_event(&event) };
return Err(XcbError::from(e));
}
_ => None,
})
}
#[cfg(feature = "serde")]
pub fn hydrate(&mut self) -> Result<()> {
self.init()
}
pub fn atom(&self, name: &str) -> Result<u32> {
if let Ok(known) = Atom::from_str(name) {
if let Some(atom) = self.atoms.get(&known) {
return Ok(*atom);
}
}
trace!(name, "interning atom");
Ok(xcb::intern_atom(&self.conn, false, name)
.get_reply()?
.atom())
}
pub fn known_atom(&self, atom: Atom) -> u32 {
*self.atoms.get(&atom).unwrap()
}
pub fn delete_prop(&self, id: Xid, prop: &str) -> Result<()> {
Ok(xcb::delete_property_checked(&self.conn, id, self.atom(prop)?).request_check()?)
}
pub fn change_prop(&self, id: Xid, prop: &str, val: Prop) -> Result<()> {
let mode = xcb::PROP_MODE_REPLACE as u8;
let a = self.atom(prop)?;
let (ty, data) = match val {
Prop::Atom(atoms) => (
xcb::xproto::ATOM_ATOM,
atoms
.iter()
.map(|a| self.atom(a))
.collect::<Result<Vec<u32>>>()?,
),
Prop::Bytes(_) => {
return Err(XcbError::InvalidPropertyData(
"unable to change non standard props".into(),
))
}
Prop::Cardinal(val) => (xcb::xproto::ATOM_CARDINAL, vec![val]),
Prop::UTF8String(strs) => {
return Ok(xcb::change_property_checked(
&self.conn,
mode,
id,
a,
xcb::xproto::ATOM_STRING,
8,
strs.join("\0").as_bytes(),
)
.request_check()?);
}
Prop::Window(ids) => (xcb::xproto::ATOM_WINDOW, ids),
Prop::WmHints(_) | Prop::WmNormalHints(_) => {
return Err(XcbError::InvalidPropertyData(
"unable to change WmHints or WmNormalHints".into(),
))
}
};
Ok(xcb::change_property_checked(&self.conn, mode, id, a, ty, 32, &data).request_check()?)
}
pub fn set_client_state(&self, id: Xid, wm_state: WindowState) -> Result<()> {
let mode = xcb::PROP_MODE_REPLACE as u8;
let a = self.known_atom(Atom::WmState);
let state = match wm_state {
WindowState::Withdrawn => 0,
WindowState::Normal => 1,
WindowState::Iconic => 3,
};
let cookie = xcb::change_property_checked(&self.conn, mode, id, a, a, 32, &[state]);
match cookie.request_check().map_err(XcbError::from) {
Err(XcbError::XcbKnown(XErrorCode::BadWindow)) => (),
other => other?,
}
Ok(())
}
pub fn create_window(&self, ty: WinType, reg: Region, managed: bool) -> Result<Xid> {
let (ty, mut data, class, root, depth, visual_id) = match ty {
WinType::CheckWin => (
None,
Vec::new(),
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
self.root,
0,
0,
),
WinType::InputOnly => (
None,
Vec::new(),
xcb::WINDOW_CLASS_INPUT_ONLY as u16,
self.root,
0,
0,
),
WinType::InputOutput(a) => {
let colormap = self.conn.generate_id();
let screen = self.screen(0)?;
let depth = self.get_depth(&screen)?;
let visual = self.get_visual_type(&depth)?;
xcb::xproto::create_colormap(
&self.conn,
xcb::COLORMAP_ALLOC_NONE as u8,
colormap,
screen.root(),
visual.visual_id(),
);
(
Some(a),
vec![
(xcb::CW_BORDER_PIXEL, screen.black_pixel()),
(xcb::CW_COLORMAP, colormap),
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS,
),
],
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root(),
depth.depth(),
visual.visual_id(),
)
}
};
if !managed {
data.push((xcb::CW_OVERRIDE_REDIRECT, 1))
}
let (x, y, w, h) = reg.values();
let id = self.conn.generate_id();
let border_width = 0;
xcb::create_window(
&self.conn, depth, id, root, x as i16,
y as i16,
w as u16,
h as u16,
border_width,
class,
visual_id,
&data,
);
if let Some(atom) = ty {
let net_name = Atom::NetWmWindowType.as_ref();
self.change_prop(id, net_name, Prop::Atom(vec![atom.as_ref().into()]))?;
self.map_client(id)?;
}
self.flush();
Ok(id)
}
pub fn configure_client(&self, id: Xid, conf: &[ClientConfig]) -> Result<()> {
let data: Vec<(u16, u32)> = conf.iter().flat_map::<Vec<_>, _>(|c| c.into()).collect();
Ok(xcb::configure_window_checked(&self.conn, id, &data).request_check()?)
}
pub fn destroy_client(&self, id: Xid) -> Result<()> {
Ok(xcb::destroy_window_checked(&self.conn, id).request_check()?)
}
pub fn kill_client(&self, id: Xid) -> Result<()> {
Ok(xcb::kill_client_checked(&self.conn, id).request_check()?)
}
pub fn map_client(&self, id: Xid) -> Result<()> {
Ok(xcb::map_window_checked(&self.conn, id).request_check()?)
}
pub fn unmap_client(&self, id: Xid) -> Result<()> {
Ok(xcb::unmap_window_checked(&self.conn, id).request_check()?)
}
pub fn focus_client(&self, id: Xid) -> Result<()> {
xcb::set_input_focus(
&self.conn, xcb::INPUT_FOCUS_PARENT as u8, id, xcb::CURRENT_TIME, );
self.change_prop(
self.root(),
Atom::NetActiveWindow.as_ref(),
Prop::Window(vec![id]),
)
}
pub fn send_client_event(&self, msg: ClientMessage) -> Result<()> {
let (dtype, d) = (self.atom(&msg.dtype)?, msg.data().as_u32());
let data = xcb::ClientMessageData::from_data32([d[0], d[1], d[2], d[3], d[4]]);
let event = xcb::ClientMessageEvent::new(32, msg.id, dtype, data);
let mask = match msg.mask {
ClientEventMask::NoEventMask => xcb::EVENT_MASK_NO_EVENT,
ClientEventMask::SubstructureNotify => xcb::EVENT_MASK_SUBSTRUCTURE_NOTIFY,
};
Ok(xcb::send_event_checked(&self.conn, false, msg.id, mask, &event).request_check()?)
}
pub fn build_client_event(
&self,
kind: ClientMessageKind,
) -> crate::core::xconnection::Result<ClientMessage> {
kind.as_message(self)
}
pub fn set_client_attributes(&self, id: Xid, attrs: &[ClientAttr]) -> Result<()> {
let data: Vec<(u32, u32)> = attrs.iter().flat_map::<Vec<_>, _>(|c| c.into()).collect();
Ok(xcb::change_window_attributes_checked(&self.conn, id, &data).request_check()?)
}
pub fn client_geometry(&self, id: Xid) -> Result<Region> {
let res = xcb::get_geometry(&self.conn, id).get_reply()?;
Ok(Region::new(
res.x() as u32,
res.y() as u32,
res.width() as u32,
res.height() as u32,
))
}
pub fn current_screens(&self) -> Result<Vec<Screen>> {
let check_win = self.check_window();
let resources = xcb::randr::get_screen_resources(&self.conn, check_win);
let screens = resources
.get_reply()?
.crtcs()
.iter()
.flat_map(|c| xcb::randr::get_crtc_info(&self.conn, *c, 0).get_reply())
.enumerate()
.map(|(i, r)| {
let region = Region::new(
r.x() as u32,
r.y() as u32,
r.width() as u32,
r.height() as u32,
);
Screen::new(region, i)
})
.filter(|s| {
let (_, _, w, _) = s.region(false).values();
w > 0
})
.collect();
self.destroy_client(check_win)?;
Ok(screens)
}
pub fn screen_sizes(&self) -> Result<Vec<Region>> {
self.current_screens()
.map(|screens| screens.iter().map(|s| s.region(false)).collect())
}
pub fn current_clients(&self) -> Result<Vec<Xid>> {
Ok(xcb::query_tree(&self.conn, self.root)
.get_reply()
.map(|reply| reply.children().into())?)
}
pub fn cursor_position(&self) -> Result<Point> {
Ok(xcb::query_pointer(&self.conn, self.root)
.get_reply()
.map(|reply| Point::new(reply.root_x() as u32, reply.root_y() as u32))?)
}
pub fn flush(&self) -> bool {
self.conn.flush()
}
pub fn focused_client(&self) -> Result<Xid> {
Ok(xcb::get_input_focus(&self.conn).get_reply()?.focus())
}
pub fn grab_keys(&self, keys: &[&KeyCode]) -> Result<()> {
let modifiers = &[0, xcb::MOD_MASK_2 as u16];
let mode = xcb::GRAB_MODE_ASYNC as u8;
for m in modifiers.iter() {
for k in keys.iter() {
xcb::grab_key_checked(
&self.conn, false, self.root, k.mask | m, k.code, mode, mode, )
.request_check()?;
}
}
self.flush();
Ok(())
}
pub fn grab_mouse_buttons(&self, states: &[&MouseState]) -> Result<()> {
let modifiers = &[0, xcb::MOD_MASK_2 as u16];
let mode = xcb::GRAB_MODE_ASYNC as u8;
let mask = (xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_BUTTON_MOTION) as u16;
for m in modifiers.iter() {
for state in states.iter() {
xcb::grab_button_checked(
&self.conn, false, self.root, mask, mode, mode, xcb::NONE, xcb::NONE, state.button(), state.mask() | m, )
.request_check()?;
}
}
self.flush();
Ok(())
}
pub fn root(&self) -> Xid {
self.root
}
pub fn check_window(&self) -> Xid {
let id = self.conn.generate_id();
xcb::create_window(
&self.conn,
0,
id,
self.root,
0,
0,
1,
1,
0,
0,
0,
&[(xcb::CW_OVERRIDE_REDIRECT, 1)],
);
self.conn.flush();
id
}
pub fn set_randr_notify_mask(&self) -> Result<()> {
let mask = (xcb::randr::NOTIFY_MASK_OUTPUT_CHANGE
| xcb::randr::NOTIFY_MASK_CRTC_CHANGE
| xcb::randr::NOTIFY_MASK_SCREEN_CHANGE) as u16;
xcb::randr::select_input_checked(&self.conn, self.root, mask).request_check()?;
self.flush();
Ok(())
}
pub fn ungrab_keys(&self) -> Result<()> {
Ok(xcb::ungrab_key_checked(
&self.conn, xcb::GRAB_ANY as u8,
self.root, xcb::MOD_MASK_ANY as u16,
)
.request_check()?)
}
pub fn ungrab_mouse_buttons(&self) -> Result<()> {
Ok(xcb::ungrab_button_checked(
&self.conn, xcb::BUTTON_INDEX_ANY as u8,
self.root, xcb::MOD_MASK_ANY as u16,
)
.request_check()?)
}
pub fn wait_for_event(&self) -> Result<XEvent> {
loop {
if let Some(event) = self.conn.wait_for_event() {
if let Some(e) = self.generic_xcb_to_xevent(event)? {
return Ok(e);
}
} else {
if let Err(e) = self.conn.has_error() {
return Err(e.into());
}
}
}
}
pub fn poll_for_event(&self) -> Result<Option<XEvent>> {
if let Some(event) = self.conn.poll_for_event() {
self.generic_xcb_to_xevent(event)
} else {
Ok(self.conn.has_error().map(|_| None)?)
}
}
pub fn warp_cursor(&self, id: Xid, x: usize, y: usize) -> Result<()> {
Ok(
xcb::warp_pointer_checked(&self.conn, 0, id, 0, 0, 0, 0, x as i16, y as i16)
.request_check()?,
)
}
}