use std::ffi::c_void;
use utils::Extent;
use xcb::{x, Xid};
use crate::window::{
input::{Keys, MouseKeys},
Events, Seat,
};
pub struct X11Window {
connection: xcb::Connection,
window: xcb::x::Window,
wm_del_window: xcb::x::Atom,
extent: (u16, u16),
}
pub struct WindowIterator<'a> {
window: &'a mut X11Window,
}
impl X11Window {
pub fn try_new(name: &str, extent: Extent, _: &str) -> Option<X11Window> {
let (connection, screen_index) = xcb::Connection::connect(None).ok()?;
let setup = connection.get_setup();
let screen = setup.roots().nth(screen_index as usize)?;
let window: xcb::x::Window = connection.generate_id();
let cookie = connection.send_request_checked(&xcb::x::CreateWindow {
depth: xcb::x::COPY_FROM_PARENT as u8,
wid: window,
parent: screen.root(),
x: 0,
y: 0,
width: extent.width() as u16, height: extent.height() as u16,
border_width: 0,
class: xcb::x::WindowClass::InputOutput,
visual: screen.root_visual(),
value_list: &[
xcb::x::Cw::BackPixel(screen.black_pixel()),
xcb::x::Cw::EventMask(xcb::x::EventMask::EXPOSURE | xcb::x::EventMask::BUTTON_PRESS | xcb::x::EventMask::BUTTON_RELEASE | xcb::x::EventMask::POINTER_MOTION | xcb::x::EventMask::ENTER_WINDOW | xcb::x::EventMask::LEAVE_WINDOW | xcb::x::EventMask::KEY_PRESS | xcb::x::EventMask::KEY_RELEASE | xcb::x::EventMask::RESIZE_REDIRECT | xcb::x::EventMask::STRUCTURE_NOTIFY),
],
});
if let Err(_) = connection.check_request(cookie) {
return None;
}
let cookie = connection.send_request_checked(&xcb::x::ChangeProperty {
mode: xcb::x::PropMode::Replace,
window,
property: xcb::x::ATOM_WM_NAME,
r#type: xcb::x::ATOM_STRING,
data: name.as_bytes(),
});
if let Err(_) = connection.check_request(cookie) {
return None;
}
let (wm_protocols, wm_del_window, _wm_state, _wm_state_maxv, _wm_state_maxh) = {
let cookies = (
connection.send_request(&xcb::x::InternAtom {
only_if_exists: true,
name: b"WM_PROTOCOLS",
}),
connection.send_request(&xcb::x::InternAtom {
only_if_exists: true,
name: b"WM_DELETE_WINDOW",
}),
connection.send_request(&xcb::x::InternAtom {
only_if_exists: true,
name: b"_NET_WM_STATE",
}),
connection.send_request(&xcb::x::InternAtom {
only_if_exists: true,
name: b"_NET_WM_STATE_MAXIMIZED_VERT",
}),
connection.send_request(&xcb::x::InternAtom {
only_if_exists: true,
name: b"_NET_WM_STATE_MAXIMIZED_HORZ",
}),
);
(
connection.wait_for_reply(cookies.0).unwrap().atom(),
connection.wait_for_reply(cookies.1).unwrap().atom(),
connection.wait_for_reply(cookies.2).unwrap().atom(),
connection.wait_for_reply(cookies.3).unwrap().atom(),
connection.wait_for_reply(cookies.4).unwrap().atom(),
)
};
let cookie = connection.send_request_checked(&xcb::x::ChangeProperty {
mode: xcb::x::PropMode::Replace,
window,
property: wm_protocols,
r#type: xcb::x::ATOM_ATOM,
data: &[wm_del_window],
});
if let Err(_) = connection.check_request(cookie) {
return None;
}
connection.send_request(&xcb::x::MapWindow { window, });
let flush_result = connection.flush();
if let Err(_) = flush_result {
return None;
}
Some(X11Window {
connection,
window,
wm_del_window,
extent: (extent.width() as u16, extent.height() as u16),
})
}
pub fn poll(&mut self) -> WindowIterator {
WindowIterator {
window: self,
}
}
pub fn get_os_handles(&self) -> OSHandles {
OSHandles {
xcb_connection: self.connection.get_raw_conn() as *mut c_void,
xcb_window: self.window.to_owned().resource_id(),
}
}
pub fn close(&self) {
let connection = &self.connection;
connection.send_request(&xcb::x::DestroyWindow { window: self.window });
}
}
pub struct OSHandles {
pub xcb_connection: *mut c_void,
pub xcb_window: u32,
}
impl Iterator for WindowIterator<'_> {
type Item = Events;
fn next(&mut self) -> Option<Events> {
let connection = &self.window.connection;
loop {
let event = connection.poll_for_event();
let event = if let Ok(event) = event {
event
} else {
return None;
};
let ev = match event? {
xcb::Event::X(x) => {
match x {
x::Event::KeyPress(ev) => {
let key: Result<Keys, _> = ev.detail().try_into();
if let Ok(key) = key {
Some(Events::Key {
seat: Seat::stub(),
pressed: true,
key,
})
} else {
None
}
},
x::Event::KeyRelease(ev) => {
let key: Result<Keys, _> = ev.detail().try_into();
if let Ok(key) = key {
Some(Events::Key {
seat: Seat::stub(),
pressed: false,
key,
})
} else {
None
}
},
x::Event::ButtonPress(ev) => {
let key: Result<MouseKeys, _> = ev.detail().try_into();
if let Ok(key) = key {
Some(Events::Button {
seat: Seat::stub(),
pressed: true,
button: key,
})
} else {
None
}
},
x::Event::ButtonRelease(ev) => {
let key: Result<MouseKeys, _> = ev.detail().try_into();
if let Ok(key) = key {
Some(Events::Button {
seat: Seat::stub(),
pressed: false,
button: key,
})
} else {
None
}
},
x::Event::MotionNotify(ev) => {
let width = self.window.extent.0 as f32;
let height = self.window.extent.1 as f32;
let x = ev.event_x() as f32;
let y = height - ev.event_y() as f32;
let half_width = width / 2.0;
let half_height = height / 2.0;
let x = (x - half_width) / half_width;
let y = (y - half_height) / half_height;
Some(Events::MousePosition {
seat: Seat::stub(),
x,
y,
time: ev.time() as u64,
})
},
x::Event::ConfigureNotify(ev) => {
if ev.width() == 0 || ev.height() == 0 {
continue;
}
let extent = (ev.width() as u16, ev.height() as u16);
if extent == self.window.extent {
continue;
}
self.window.extent = extent;
Some(Events::Resize{
width: extent.0 as u32,
height: extent.1 as u32,
})
},
x::Event::ClientMessage(ev) => {
if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
if atom == self.window.wm_del_window.resource_id() {
let event = Events::Close;
Some(event)
} else {
None
}
} else {
None
}
},
_ => { None }
}
}
_ => { None }
};
if let Some(ev) = ev {
return Some(ev);
} else {
continue;
}
}
}
}