use crate::data_types::{KeyBindings, KeyCode, Region, WinId};
use crate::screen::Screen;
use xcb;
const NOTIFY_MASK: u16 = xcb::randr::NOTIFY_MASK_CRTC_CHANGE as u16;
const GRAB_MODE_ASYNC: u8 = xcb::GRAB_MODE_ASYNC as u8;
const INPUT_FOCUS_PARENT: u8 = xcb::INPUT_FOCUS_PARENT as u8;
const PROP_MODE_REPLACE: u8 = xcb::PROP_MODE_REPLACE as u8;
const ATOM_WINDOW: u32 = xcb::xproto::ATOM_WINDOW;
const WIN_BORDER: u16 = xcb::CONFIG_WINDOW_BORDER_WIDTH as u16;
const WIN_HEIGHT: u16 = xcb::CONFIG_WINDOW_HEIGHT as u16;
const WIN_WIDTH: u16 = xcb::CONFIG_WINDOW_WIDTH as u16;
const WIN_X: u16 = xcb::CONFIG_WINDOW_X as u16;
const WIN_Y: u16 = xcb::CONFIG_WINDOW_Y as u16;
const NEW_WINDOW_MASK: &[(u32, u32)] = &[(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_ENTER_WINDOW | xcb::EVENT_MASK_LEAVE_WINDOW,
)];
const MOUSE_MASK: u16 = (xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_POINTER_MOTION) as u16;
const EVENT_MASK: &[(u32, u32)] = &[(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_SUBSTRUCTURE_NOTIFY as u32,
)];
#[derive(Debug, Copy, Clone)]
pub enum XEvent {
ButtonPress,
ButtonRelease,
KeyPress(KeyCode),
Map(WinId),
Enter(WinId),
Leave(WinId),
Motion,
Destroy(WinId),
}
pub trait XConn {
fn flush(&self) -> bool;
fn wait_for_event(&self) -> Option<XEvent>;
fn current_outputs(&self) -> Vec<Screen>;
fn position_window(&self, id: WinId, r: Region, border: u32);
fn mark_new_window(&self, id: WinId);
fn map_window(&self, id: WinId);
fn unmap_window(&self, id: WinId);
fn send_client_event(&self, id: WinId, atom_name: &str);
fn focus_client(&self, id: WinId);
fn set_client_border_color(&self, id: WinId, color: u32);
fn grab_keys(&self, key_bindings: &KeyBindings);
fn intern_atom(&self, name: &str) -> u32;
fn str_prop(&self, id: u32, name: &str) -> Result<String, String>;
fn atom_prop(&self, id: u32, name: &str) -> Result<u32, String>;
}
pub struct XcbConnection {
conn: xcb::Connection,
}
impl XcbConnection {
pub fn new() -> XcbConnection {
let (conn, _) = match xcb::Connection::connect(None) {
Err(e) => die!("unable to establish connection to X server: {}", e),
Ok(conn) => conn,
};
XcbConnection { conn }
}
}
impl XConn for XcbConnection {
fn flush(&self) -> bool {
self.conn.flush()
}
fn wait_for_event(&self) -> Option<XEvent> {
self.conn.wait_for_event().and_then(|event| {
let etype = event.response_type();
match etype {
xcb::BUTTON_PRESS => None,
xcb::BUTTON_RELEASE => None,
xcb::KEY_PRESS => {
let e: &xcb::KeyPressEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::KeyPress(KeyCode::from_key_press(e)))
}
xcb::MAP_NOTIFY => {
let e: &xcb::MapNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Map(e.window()))
}
xcb::ENTER_NOTIFY => {
let e: &xcb::EnterNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Enter(e.event()))
}
xcb::LEAVE_NOTIFY => {
let e: &xcb::LeaveNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Leave(e.event()))
}
xcb::MOTION_NOTIFY => None,
xcb::DESTROY_NOTIFY => {
let e: &xcb::MapNotifyEvent = unsafe { xcb::cast_event(&event) };
Some(XEvent::Destroy(e.event()))
}
_ => None,
}
})
}
fn current_outputs(&self) -> Vec<Screen> {
let screen = match self.conn.get_setup().roots().nth(0) {
None => die!("unable to get handle for screen"),
Some(s) => s,
};
let win_id = self.conn.generate_id();
let root = screen.root();
xcb::create_window(
&self.conn,
0,
win_id,
root,
0,
0,
1,
1,
0,
0,
0,
&[],
);
let resources = xcb::randr::get_screen_resources(&self.conn, win_id);
match resources.get_reply() {
Err(e) => die!("error reading X screen resources: {}", e),
Ok(reply) => reply
.crtcs()
.iter()
.flat_map(|c| xcb::randr::get_crtc_info(&self.conn, *c, 0).get_reply())
.enumerate()
.map(|(i, r)| Screen::from_crtc_info_reply(r, i))
.filter(|s| s.region.width() > 0)
.collect(),
}
}
fn position_window(&self, id: WinId, r: Region, border: u32) {
let (x, y, w, h) = r.values();
xcb::configure_window(
&self.conn,
id,
&[
(WIN_X, x),
(WIN_Y, y),
(WIN_WIDTH, w),
(WIN_HEIGHT, h),
(WIN_BORDER, border),
],
);
}
fn mark_new_window(&self, id: WinId) {
xcb::change_window_attributes(&self.conn, id, NEW_WINDOW_MASK);
}
fn map_window(&self, id: WinId) {
xcb::map_window(&self.conn, id);
}
fn unmap_window(&self, id: WinId) {
xcb::unmap_window(&self.conn, id);
}
fn send_client_event(&self, id: WinId, atom_name: &str) {
let atom = self.intern_atom(atom_name);
let wm_protocols = self.intern_atom("WM_PROTOCOLS");
let data = xcb::ClientMessageData::from_data32([atom, xcb::CURRENT_TIME, 0, 0, 0]);
let event = xcb::ClientMessageEvent::new(32, id, wm_protocols, data);
xcb::send_event(&self.conn, false, id, xcb::EVENT_MASK_NO_EVENT, &event);
}
fn focus_client(&self, id: WinId) {
let root = match self.conn.get_setup().roots().nth(0) {
None => die!("unable to get handle for screen"),
Some(screen) => screen.root(),
};
let prop = self.intern_atom("_NET_ACTIVE_WINDOW");
xcb::set_input_focus(
&self.conn,
INPUT_FOCUS_PARENT,
id,
0,
);
xcb::change_property(
&self.conn,
PROP_MODE_REPLACE,
root,
prop,
ATOM_WINDOW,
32,
&[id],
);
}
fn set_client_border_color(&self, id: WinId, color: u32) {
xcb::change_window_attributes(&self.conn, id, &[(xcb::CW_BORDER_PIXEL, color)]);
}
fn grab_keys(&self, key_bindings: &KeyBindings) {
let screen = self.conn.get_setup().roots().nth(0).unwrap();
let root = screen.root();
let input = xcb::randr::select_input(&self.conn, root, NOTIFY_MASK);
match input.request_check() {
Err(e) => die!("randr error: {}", e),
Ok(_) => {
for k in key_bindings.keys() {
xcb::grab_key(
&self.conn,
false,
root,
k.mask,
k.code,
GRAB_MODE_ASYNC,
GRAB_MODE_ASYNC,
);
}
}
}
for mouse_button in &[1, 3] {
xcb::grab_button(
&self.conn,
false,
root,
MOUSE_MASK,
GRAB_MODE_ASYNC,
GRAB_MODE_ASYNC,
xcb::NONE,
xcb::NONE,
*mouse_button,
xcb::MOD_MASK_4 as u16,
);
}
xcb::change_window_attributes(&self.conn, root, EVENT_MASK);
&self.conn.flush();
}
fn intern_atom(&self, name: &str) -> u32 {
let interned_atom = xcb::intern_atom(
&self.conn,
false,
name,
);
match interned_atom.get_reply() {
Err(e) => die!("unable to fetch xcb atom '{}': {}", name, e),
Ok(reply) => reply.atom(),
}
}
fn str_prop(&self, id: u32, name: &str) -> Result<String, String> {
let cookie = xcb::get_property(
&self.conn,
false,
id,
self.intern_atom(name),
xcb::ATOM_ANY,
0,
1024,
);
match cookie.get_reply() {
Err(e) => Err(format!("unable to fetch window property: {}", e)),
Ok(reply) => match String::from_utf8(reply.value().to_vec()) {
Err(e) => Err(format!("invalid utf8 resonse from xcb: {}", e)),
Ok(s) => Ok(s),
},
}
}
fn atom_prop(&self, id: u32, name: &str) -> Result<u32, String> {
let cookie = xcb::get_property(
&self.conn,
false,
id,
self.intern_atom(name),
xcb::ATOM_ANY,
0,
1024,
);
match cookie.get_reply() {
Err(e) => Err(format!("unable to fetch window property: {}", e)),
Ok(reply) => {
if reply.value_len() <= 0 {
Err(format!("property '{}' was empty for id: {}", name, id))
} else {
Ok(reply.value()[0])
}
}
}
}
}
pub struct MockXConn {
screens: Vec<Screen>,
}
impl MockXConn {
pub fn new(screens: Vec<Screen>) -> Self {
MockXConn { screens }
}
}
impl XConn for MockXConn {
fn flush(&self) -> bool {
true
}
fn wait_for_event(&self) -> Option<XEvent> {
None
}
fn current_outputs(&self) -> Vec<Screen> {
self.screens.clone()
}
fn position_window(&self, _: WinId, _: Region, _: u32) {}
fn mark_new_window(&self, _: WinId) {}
fn map_window(&self, _: WinId) {}
fn unmap_window(&self, _: WinId) {}
fn send_client_event(&self, _: WinId, _: &str) {}
fn focus_client(&self, _: WinId) {}
fn set_client_border_color(&self, _: WinId, _: u32) {}
fn grab_keys(&self, _: &KeyBindings) {}
fn intern_atom(&self, _: &str) -> u32 {
0
}
fn str_prop(&self, _: u32, name: &str) -> Result<String, String> {
Ok(String::from(name))
}
fn atom_prop(&self, id: u32, _: &str) -> Result<u32, String> {
Ok(id)
}
}